Skip to content

Commit

Permalink
Refactor registry and encoding (#50)
Browse files Browse the repository at this point in the history
* feat(encoding): add library to handle encoding

Create a new library for general type encoding

* feat(registry): add simple registry

add a simple threadsafe registry to handle registering types like vouchers

* feat(datatransfer): incorporate registry

incorporate registry into data transfer module

* feat(message): move decoding inside messages

Move encode/decode inside the message -- so that we can insure cbor encodable values, and then use
cbg.Deferred later

* feat(message): convert to cbg.Deferred

convert messages to use cbg.Deferred so that the whole message is a proper CBOR value and also move
selector encoding inside the message

* refactor(graphsync): remove utils

remove utils.go which is not used

* refactor(registry): move types to root

move Identifier & Registerable to root types package

* refactor(datatransfer): minor refactors and cleanups
  • Loading branch information
hannahhoward committed May 1, 2020
1 parent 8b34b63 commit 9a1d95f
Show file tree
Hide file tree
Showing 26 changed files with 839 additions and 402 deletions.
18 changes: 2 additions & 16 deletions channels/channels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,13 @@ import (
"github.com/stretchr/testify/require"
)

type fakeVoucher struct{}

func (fv *fakeVoucher) ToBytes() ([]byte, error) {
panic("not implemented")
}

func (fv *fakeVoucher) FromBytes(_ []byte) error {
panic("not implemented")
}

func (fv *fakeVoucher) Type() string {
panic("not implemented")
}

func TestChannels(t *testing.T) {
channels := channels.New()

tid1 := datatransfer.TransferID(0)
tid2 := datatransfer.TransferID(1)
fv1 := &fakeVoucher{}
fv2 := &fakeVoucher{}
fv1 := &testutil.FakeDTType{}
fv2 := &testutil.FakeDTType{}
cids := testutil.GenerateCids(2)
selector := builder.NewSelectorSpecBuilder(basicnode.Style.Any).Matcher().Node()
peers := testutil.GeneratePeers(4)
Expand Down
122 changes: 122 additions & 0 deletions encoding/encoding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package encoding

import (
"bytes"
"reflect"

cbor "github.com/ipfs/go-ipld-cbor"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/codec/dagcbor"
cborgen "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
)

// Encodable is an object that can be written to CBOR and decoded back
type Encodable interface{}

// Encode encodes an encodable to CBOR, using the best available path for
// writing to CBOR
func Encode(value Encodable) ([]byte, error) {
if cbgEncodable, ok := value.(cborgen.CBORMarshaler); ok {
buf := new(bytes.Buffer)
err := cbgEncodable.MarshalCBOR(buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
if ipldEncodable, ok := value.(ipld.Node); ok {
buf := new(bytes.Buffer)
err := dagcbor.Encoder(ipldEncodable, buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
return cbor.DumpObject(value)
}

// Decoder is CBOR decoder for a given encodable type
type Decoder interface {
DecodeFromCbor([]byte) (Encodable, error)
}

// NewDecoder creates a new Decoder that will decode into new instances of the given
// object type. It will use the decoding that is optimal for that type
// It returns error if it's not possible to setup a decoder for this type
func NewDecoder(decodeType Encodable) (Decoder, error) {
// check if type is ipld.Node, if so, just use style
if ipldDecodable, ok := decodeType.(ipld.Node); ok {
return &ipldDecoder{ipldDecodable.Style()}, nil
}
// check if type is a pointer, as we need that to make new copies
// for cborgen types & regular IPLD types
decodeReflectType := reflect.TypeOf(decodeType)
if decodeReflectType.Kind() != reflect.Ptr {
return nil, xerrors.New("type must be a pointer")
}
// check if type is a cbor-gen type
if _, ok := decodeType.(cborgen.CBORUnmarshaler); ok {
return &cbgDecoder{decodeReflectType}, nil
}
// type does is neither ipld-prime nor cbor-gen, so we need to see if it
// can rountrip with oldschool ipld-format
encoded, err := cbor.DumpObject(decodeType)
if err != nil {
return nil, xerrors.New("Object type did not encode")
}
newDecodable := reflect.New(decodeReflectType.Elem()).Interface()
if err := cbor.DecodeInto(encoded, newDecodable); err != nil {
return nil, xerrors.New("Object type did not decode")
}
return &defaultDecoder{decodeReflectType}, nil
}

type ipldDecoder struct {
style ipld.NodeStyle
}

func (decoder *ipldDecoder) DecodeFromCbor(encoded []byte) (Encodable, error) {
builder := decoder.style.NewBuilder()
buf := bytes.NewReader(encoded)
err := dagcbor.Decoder(builder, buf)
if err != nil {
return nil, err
}
return builder.Build(), nil
}

type cbgDecoder struct {
cbgType reflect.Type
}

func (decoder *cbgDecoder) DecodeFromCbor(encoded []byte) (Encodable, error) {
decodedValue := reflect.New(decoder.cbgType.Elem())
decoded, ok := decodedValue.Interface().(cborgen.CBORUnmarshaler)
if !ok || reflect.ValueOf(decoded).IsNil() {
return nil, xerrors.New("problem instantiating decoded value")
}
buf := bytes.NewReader(encoded)
err := decoded.UnmarshalCBOR(buf)
if err != nil {
return nil, err
}
return decoded, nil
}

type defaultDecoder struct {
ptrType reflect.Type
}

func (decoder *defaultDecoder) DecodeFromCbor(encoded []byte) (Encodable, error) {
decodedValue := reflect.New(decoder.ptrType.Elem())
decoded, ok := decodedValue.Interface().(Encodable)
if !ok || reflect.ValueOf(decoded).IsNil() {
return nil, xerrors.New("problem instantiating decoded value")
}
err := cbor.DecodeInto(encoded, decoded)
if err != nil {
return nil, err
}
return decoded, nil
}
37 changes: 37 additions & 0 deletions encoding/encoding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package encoding_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/filecoin-project/go-data-transfer/encoding"
"github.com/filecoin-project/go-data-transfer/encoding/testdata"
)

func TestRoundTrip(t *testing.T) {
testCases := map[string]struct {
val encoding.Encodable
}{
"can encode/decode IPLD prime types": {
val: testdata.Prime,
},
"can encode/decode cbor-gen types": {
val: testdata.Cbg,
},
"can encode/decode old ipld format types": {
val: testdata.Standard,
},
}
for testCase, data := range testCases {
t.Run(testCase, func(t *testing.T) {
encoded, err := encoding.Encode(data.val)
require.NoError(t, err)
decoder, err := encoding.NewDecoder(data.val)
require.NoError(t, err)
decoded, err := decoder.DecodeFromCbor(encoded)
require.NoError(t, err)
require.Equal(t, data.val, decoded)
})
}
}
37 changes: 37 additions & 0 deletions encoding/testdata/testdata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package testdata

import (
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/ipld/go-ipld-prime/fluent"
basicnode "github.com/ipld/go-ipld-prime/node/basic"
)

// Prime = an instance of an ipld prime piece of data
var Prime = fluent.MustBuildMap(basicnode.Style.Map, 2, func(na fluent.MapAssembler) {
nva := na.AssembleEntry("X")
nva.AssignInt(100)
nva = na.AssembleEntry("Y")
nva.AssignString("appleSauce")
})

type standardType struct {
X int
Y string
}

func init() {
cbor.RegisterCborType(standardType{})
}

// Standard = an instance that is neither ipld prime nor cbor
var Standard *standardType = &standardType{X: 100, Y: "appleSauce"}

//go:generate cbor-gen-for cbgType

type cbgType struct {
X uint64
Y string
}

// Cbg = an instance of a cbor-gen type
var Cbg *cbgType = &cbgType{X: 100, Y: "appleSauce"}
84 changes: 84 additions & 0 deletions encoding/testdata/testdata_cbor_gen.go

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

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/ipfs/go-ipfs-chunker v0.0.5
github.com/ipfs/go-ipfs-exchange-offline v0.0.1
github.com/ipfs/go-ipfs-files v0.0.8
github.com/ipfs/go-ipld-cbor v0.0.4
github.com/ipfs/go-ipld-format v0.2.0
github.com/ipfs/go-log v1.0.2
github.com/ipfs/go-merkledag v0.3.1
Expand Down
5 changes: 2 additions & 3 deletions impl/dagservice/dagservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package datatransfer

import (
"context"
"reflect"
"time"

"github.com/ipfs/go-cid"
Expand All @@ -12,7 +11,7 @@ import (
"github.com/libp2p/go-libp2p-core/peer"
"golang.org/x/xerrors"

"github.com/filecoin-project/go-data-transfer"
datatransfer "github.com/filecoin-project/go-data-transfer"
)

// This file implements a VERY simple, incomplete version of the data transfer
Expand All @@ -38,7 +37,7 @@ func NewDAGServiceDataTransfer(dag ipldformat.DAGService) datatransfer.Manager {
// RegisterVoucherType registers a validator for the given voucher type
// will error if voucher type does not implement voucher
// or if there is a voucher type registered with an identical identifier
func (impl *dagserviceImpl) RegisterVoucherType(voucherType reflect.Type, validator datatransfer.RequestValidator) error {
func (impl *dagserviceImpl) RegisterVoucherType(voucherType datatransfer.Voucher, validator datatransfer.RequestValidator) error {
return nil
}

Expand Down

0 comments on commit 9a1d95f

Please sign in to comment.