Skip to content

Commit

Permalink
Merge pull request ethereum#214 from ethersphere/swarm-mutableresourc…
Browse files Browse the repository at this point in the history
…es-multihash

Infer multihash resource update content when datalength header is 0
  • Loading branch information
gbalint authored Feb 20, 2018
2 parents a1a1838 + 05e09eb commit a375d95
Show file tree
Hide file tree
Showing 50 changed files with 6,328 additions and 17 deletions.
6 changes: 0 additions & 6 deletions swarm/api/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,22 +415,16 @@ func (s *Server) translateResourceError(w http.ResponseWriter, r *Request, supEr
}
switch code {
case storage.ErrInvalidValue:
//s.BadRequest(w, r, defaultErr.Error())
return http.StatusBadRequest, defaultErr
case storage.ErrNotFound, storage.ErrNotSynced, storage.ErrNothingToReturn:
//s.NotFound(w, r, defaultErr)
return http.StatusNotFound, defaultErr
case storage.ErrUnauthorized, storage.ErrInvalidSignature:
//ShowError(w, &r.Request, defaultErr.Error(), http.StatusUnauthorized)
return http.StatusUnauthorized, defaultErr
case storage.ErrDataOverflow:
//ShowError(w, &r.Request, defaultErr.Error(), http.StatusRequestEntityTooLarge)
return http.StatusRequestEntityTooLarge, defaultErr
}

return http.StatusInternalServerError, defaultErr

//s.Error(w, r, defaultErr)
}

// HandleGet handles a GET request to
Expand Down
73 changes: 63 additions & 10 deletions swarm/storage/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package storage
import (
"context"
"encoding/binary"
"errors"
"fmt"
"math/big"
"path/filepath"
Expand Down Expand Up @@ -44,8 +45,11 @@ func NewResourceError(code int, s string) error {
panic("no such error code!")
}
r := &ResourceError{
err: s,
code: code,
err: s,
}
switch code {
case ErrNotFound, ErrIO, ErrUnauthorized, ErrInvalidValue, ErrDataOverflow, ErrNothingToReturn, ErrInvalidSignature, ErrNotSynced:
r.code = code
}
return r
}
Expand Down Expand Up @@ -513,7 +517,17 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32,
namelength := int(headerlength) - cursor + 4
name = string(chunkdata[cursor : cursor+namelength])
cursor += namelength
intdatalength := int(datalength)
var intdatalength int
if datalength == 0 {
intdatalength = isMultihash(chunkdata[cursor:])
multihashboundary := cursor + intdatalength
if len(chunkdata) != multihashboundary && len(chunkdata) < multihashboundary+signatureLength {
log.Debug("multihash error", "chunkdatalen", len(chunkdata), "multihashboundary", multihashboundary)
return nil, 0, 0, "", nil, errors.New("Corrupt multihash data")
}
} else {
intdatalength = int(datalength)
}
data = make([]byte, intdatalength)
copy(data, chunkdata[cursor:cursor+intdatalength])

Expand All @@ -534,7 +548,19 @@ func (self *ResourceHandler) parseUpdate(chunkdata []byte) (*Signature, uint32,
// It is the caller's responsibility to make sure that this data is not stale.
//
// A resource update cannot span chunks, and thus has max length 4096

func (self *ResourceHandler) UpdateMultihash(ctx context.Context, name string, data []byte) (Key, error) {
if isMultihash(data) == 0 {
return nil, NewResourceError(ErrNothingToReturn, "Invalid multihash")
}
return self.update(ctx, name, data, true)
}

func (self *ResourceHandler) Update(ctx context.Context, name string, data []byte) (Key, error) {
return self.update(ctx, name, data, false)
}

func (self *ResourceHandler) update(ctx context.Context, name string, data []byte, multihash bool) (Key, error) {

var signaturelength int
if self.validator != nil {
Expand Down Expand Up @@ -599,7 +625,11 @@ func (self *ResourceHandler) Update(ctx context.Context, name string, data []byt
}
}

chunk := newUpdateChunk(key, signature, nextperiod, version, name, data)
var datalength int
if !multihash {
datalength = len(data)
}
chunk := newUpdateChunk(key, signature, nextperiod, version, name, data, datalength)

// send the chunk
self.Put(chunk)
Expand Down Expand Up @@ -684,7 +714,7 @@ func getAddressFromDataSig(datahash common.Hash, signature Signature) (common.Ad
}

// create an update chunk
func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32, name string, data []byte) *Chunk {
func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32, name string, data []byte, datalength int) *Chunk {

// no signatures if no validator
var signaturelength int
Expand All @@ -695,11 +725,9 @@ func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32
// prepend version and period to allow reverse lookups
headerlength := len(name) + 4 + 4

// also prepend datalength
datalength := len(data)

actualdatalength := len(data)
chunk := NewChunk(key, nil)
chunk.SData = make([]byte, 4+signaturelength+headerlength+datalength) // initial 4 are uint16 length descriptors for headerlength and datalength
chunk.SData = make([]byte, 4+signaturelength+headerlength+actualdatalength) // initial 4 are uint16 length descriptors for headerlength and datalength

// data header length does NOT include the header length prefix bytes themselves
cursor := 0
Expand All @@ -726,7 +754,7 @@ func newUpdateChunk(key Key, signature *Signature, period uint32, version uint32

// if signature is present it's the last item in the chunk data
if signature != nil {
cursor += datalength
cursor += actualdatalength
copy(chunk.SData[cursor:], signature[:])
}

Expand Down Expand Up @@ -812,6 +840,31 @@ func (self *ResourceHandler) keyDataHash(key Key, data []byte) common.Hash {
return common.BytesToHash(hasher.Sum(nil))
}

// if first byte is the start of a multihash this function will try to parse it
// if successful it returns the length of multihash data, 0 otherwise
func isMultihash(data []byte) int {
cursor := 0
_, c := binary.Uvarint(data)
if c <= 0 {
log.Warn("Corrupt multihash data, hashtype is unreadable")
return 0
}
cursor += c
hashlength, c := binary.Uvarint(data[cursor:])
if c <= 0 {
log.Warn("Corrupt multihash data, hashlength is unreadable")
return 0
}
cursor += c
// we cheekily assume hashlength < maxint
inthashlength := int(hashlength)
if len(data[cursor:]) < inthashlength {
log.Warn("Corrupt multihash data, hash does not align with data boundary")
return 0
}
return cursor + inthashlength
}

// TODO: this should not be exposed, but swarm/testutil/http.go needs it
func NewTestResourceHandler(datadir string, ethClient ethApi, validator ResourceValidator) (*ResourceHandler, error) {
path := filepath.Join(datadir, DbDirName)
Expand Down
143 changes: 142 additions & 1 deletion swarm/storage/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"testing"
"time"

"github.com/multiformats/go-multihash"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -107,7 +109,7 @@ func TestResourceReverse(t *testing.T) {
t.Fatal(err)
}

chunk := newUpdateChunk(key, &sig, period, version, safeName, data)
chunk := newUpdateChunk(key, &sig, period, version, safeName, data, len(data))

// check that we can recover the owner account from the update chunk's signature
checksig, checkperiod, checkversion, checkname, checkdata, err := rh.parseUpdate(chunk.SData)
Expand Down Expand Up @@ -262,6 +264,133 @@ func TestResourceHandler(t *testing.T) {

}

func TestResourceMultihash(t *testing.T) {

// signer containing private key
signer, err := newTestSigner()
if err != nil {
t.Fatal(err)
}
validator := newTestValidator(signer.signContent)

// make fake backend, set up rpc and create resourcehandler
backend := &fakeBackend{
blocknumber: int64(startBlock),
}

// set up rpc and create resourcehandler
rh, datadir, _, teardownTest, err := setupTest(backend, nil)
if err != nil {
t.Fatal(err)
}
defer teardownTest()

// create a new resource
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err = rh.NewResource(ctx, safeName, resourceFrequency)
if err != nil {
t.Fatal(err)
}

// we're naïvely assuming keccak256 for swarm hashes
// if it ever changes this test should also change
swarmhashbytes := rh.nameHash("foo")
swarmhashmulti, err := multihash.Encode(swarmhashbytes.Bytes(), multihash.KECCAK_256)
if err != nil {
t.Fatal(err)
}
swarmhashkey, err := rh.UpdateMultihash(ctx, safeName, swarmhashmulti)
if err != nil {
t.Fatal(err)
}

sha1bytes := make([]byte, multihash.DefaultLengths[multihash.SHA1])
sha1multi, err := multihash.Encode(sha1bytes, multihash.SHA1)
if err != nil {
t.Fatal(err)
}
sha1key, err := rh.UpdateMultihash(ctx, safeName, sha1multi)
if err != nil {
t.Fatal(err)
}

// invalid multihashes
_, err = rh.UpdateMultihash(ctx, safeName, swarmhashmulti[1:])
if err == nil {
t.Fatalf("Expected update to fail with first byte skipped")
}
_, err = rh.UpdateMultihash(ctx, safeName, swarmhashmulti[:len(swarmhashmulti)-2])
if err == nil {
t.Fatalf("Expected update to fail with last byte skipped")
}

data, err := getUpdateDirect(rh, swarmhashkey)
if err != nil {
t.Fatal(err)
}
swarmhashdecode, err := multihash.Decode(data)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(swarmhashdecode.Digest, swarmhashbytes.Bytes()) {
t.Fatalf("Decoded SHA1 hash '%x' does not match original hash '%x'", swarmhashdecode.Digest, swarmhashbytes.Bytes())
}
data, err = getUpdateDirect(rh, sha1key)
if err != nil {
t.Fatal(err)
}
sha1decode, err := multihash.Decode(data)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(sha1decode.Digest, sha1bytes) {
t.Fatalf("Decoded SHA1 hash '%x' does not match original hash '%x'", sha1decode.Digest, sha1bytes)
}
rh.Close()

// test with signed data
rh2, err := NewTestResourceHandler(datadir, rh.ethClient, validator)
if err != nil {
t.Fatal(err)
}
_, err = rh2.NewResource(ctx, safeName, resourceFrequency)
if err != nil {
t.Fatal(err)
}
swarmhashsignedkey, err := rh2.UpdateMultihash(ctx, safeName, swarmhashmulti)
if err != nil {
t.Fatal(err)
}
sha1signedkey, err := rh2.UpdateMultihash(ctx, safeName, sha1multi)
if err != nil {
t.Fatal(err)
}

data, err = getUpdateDirect(rh2, swarmhashsignedkey)
if err != nil {
t.Fatal(err)
}
swarmhashdecode, err = multihash.Decode(data)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(swarmhashdecode.Digest, swarmhashbytes.Bytes()) {
t.Fatalf("Decoded SHA1 hash '%x' does not match original hash '%x'", swarmhashdecode.Digest, swarmhashbytes.Bytes())
}
data, err = getUpdateDirect(rh2, sha1signedkey)
if err != nil {
t.Fatal(err)
}
sha1decode, err = multihash.Decode(data)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(sha1decode.Digest, sha1bytes) {
t.Fatalf("Decoded SHA1 hash '%x' does not match original hash '%x'", sha1decode.Digest, sha1bytes)
}
}

// create ENS enabled resource update, with and without valid owner
func TestResourceENSOwner(t *testing.T) {

Expand Down Expand Up @@ -444,3 +573,15 @@ func (self *testValidator) checkAccess(name string, address common.Address) (boo
func (self *testValidator) nameHash(name string) common.Hash {
return self.hashFunc(name)
}

func getUpdateDirect(rh *ResourceHandler, key Key) ([]byte, error) {
chunk, err := rh.ChunkStore.(*resourceChunkStore).localStore.(*LocalStore).memStore.Get(key)
if err != nil {
return nil, err
}
_, _, _, _, data, err := rh.parseUpdate(chunk.SData)
if err != nil {
return nil, err
}
return data, nil
}
13 changes: 13 additions & 0 deletions vendor/github.com/jbenet/go-base58/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a375d95

Please sign in to comment.