Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

introduce LinkSystem #143

Merged
merged 10 commits into from
Mar 12, 2021
16 changes: 5 additions & 11 deletions codec/api.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package codec

import (
"io"

"github.com/ipld/go-ipld-prime"
)

// Encoder is the essential definition of a function that takes IPLD Data Model data in memory and serializes it.
// IPLD Codecs are written by implementing this function interface (as well as (typically) a matched Decoder).
// Encoder is defined in the root ipld package; this alias is just for documentation and discoverability.
//
// Encoder functions can be composed into an ipld.LinkSystem to provide
// a "one stop shop" API for handling content addressable storage.
Expand All @@ -33,15 +30,12 @@ import (
// in all scenarios that use codecs indirectly.
// There is also no standard interface for such configurations: by nature,
// if they exist at all, they vary per codec.
type Encoder func(data ipld.Node, output io.Writer) error
type Encoder = ipld.Encoder

// Decoder is the essential definiton of a function that consumes serial data and unfurls it into IPLD Data Model-compatible in-memory representations.
// IPLD Codecs are written by implementing this function interface (as well as (typically) a matched Encoder).
// Decoder is defined in the root ipld package; this alias is just for documentation and discoverability.
//
// Decoder is the dual of Encoder.
// Most of the documentation for the Encoder function interface
// also applies wholesale to the Decoder interface.
type Decoder func(into ipld.NodeAssembler, input io.Reader) error
// Most of the documentation for Encoder also applies wholesale to the Decoder interface.
type Decoder = ipld.Decoder

type ErrBudgetExhausted struct{}

Expand Down
18 changes: 8 additions & 10 deletions codec/dagcbor/multicodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,22 @@ import (

"github.com/polydawn/refmt/cbor"

ipld "github.com/ipld/go-ipld-prime"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/multicodec"
)

var (
_ cidlink.MulticodecDecoder = Decoder
_ cidlink.MulticodecEncoder = Encoder
_ ipld.Decoder = Decode
_ ipld.Encoder = Encode
)

func init() {
cidlink.RegisterMulticodecDecoder(0x71, Decoder)
cidlink.RegisterMulticodecEncoder(0x71, Encoder)
multicodec.EncoderRegistry[0x71] = Encode
multicodec.DecoderRegistry[0x71] = Decode
}

func Decoder(na ipld.NodeAssembler, r io.Reader) error {
func Decode(na ipld.NodeAssembler, r io.Reader) error {
// Probe for a builtin fast path. Shortcut to that if possible.
// (ipldcbor.NodeBuilder supports this, for example.)
type detectFastPath interface {
DecodeDagCbor(io.Reader) error
}
Expand All @@ -32,9 +31,8 @@ func Decoder(na ipld.NodeAssembler, r io.Reader) error {
return Unmarshal(na, cbor.NewDecoder(cbor.DecodeOptions{}, r))
}

func Encoder(n ipld.Node, w io.Writer) error {
func Encode(n ipld.Node, w io.Writer) error {
// Probe for a builtin fast path. Shortcut to that if possible.
// (ipldcbor.Node supports this, for example.)
type detectFastPath interface {
EncodeDagCbor(io.Writer) error
}
Expand Down
28 changes: 13 additions & 15 deletions codec/dagcbor/roundtripCidlink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package dagcbor

import (
"bytes"
"context"
"io"
"testing"

Expand All @@ -15,27 +14,26 @@ import (
)

func TestRoundtripCidlink(t *testing.T) {
lb := cidlink.LinkBuilder{cid.Prefix{
lp := cidlink.LinkPrototype{cid.Prefix{
Version: 1,
Codec: 0x71,
MhType: 0x17,
MhType: 0x13,
MhLength: 4,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this not be inferred from the type?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ not ( :) it's an adapter type, not an interface cast.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think i was wanting the apparently deprecated '-1' to indicate default length because a lot of these hashes have a single length that makes sense, and i don't want to be remembering / fumble that sha1 should be 20 bytes while sha224 should be 28 bytes every time i make one of these.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, sorry, I thought the comment was on the diff line, since that's what github highlighted most brightly.

Yeah, agree. I wish the go-cid and/or go-multihash libraries were more friendly about this very common user story.

I think a -1 should flow through and do whatever go-multihash does, still. And I have no idea why that's deprecated, fwiw. (A lot of things in go-multihash seem deprecated without much comment on why or what to do instead. I think some review and renovation of that is overdue.)

It's slightly on the other side of where I'm cordoning my renovation today, though.

}}
lsys := cidlink.DefaultLinkSystem()

buf := bytes.Buffer{}
lnk, err := lb.Build(context.Background(), ipld.LinkContext{}, n,
func(ipld.LinkContext) (io.Writer, ipld.StoreCommitter, error) {
return &buf, func(lnk ipld.Link) error { return nil }, nil
},
)
lsys.StorageWriteOpener = func(lnkCtx ipld.LinkContext) (io.Writer, ipld.BlockWriteCommitter, error) {
return &buf, func(lnk ipld.Link) error { return nil }, nil
}
lsys.StorageReadOpener = func(lnkCtx ipld.LinkContext, lnk ipld.Link) (io.Reader, error) {
return bytes.NewReader(buf.Bytes()), nil
}

lnk, err := lsys.Store(ipld.LinkContext{}, lp, n)
Require(t, err, ShouldEqual, nil)

nb := basicnode.Prototype__Any{}.NewBuilder()
err = lnk.Load(context.Background(), ipld.LinkContext{}, nb,
func(lnk ipld.Link, _ ipld.LinkContext) (io.Reader, error) {
return bytes.NewReader(buf.Bytes()), nil
},
)
n2, err := lsys.Load(ipld.LinkContext{}, lnk, basicnode.Prototype.Any)
Require(t, err, ShouldEqual, nil)
Wish(t, nb.Build(), ShouldEqual, n)
Wish(t, n2, ShouldEqual, n)
}
30 changes: 10 additions & 20 deletions codec/dagcbor/roundtrip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ package dagcbor

import (
"bytes"
"context"
"crypto/rand"
"io"
"strings"
"testing"

cid "github.com/ipfs/go-cid"
. "github.com/warpfork/go-wish"

ipld "github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/fluent"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
basicnode "github.com/ipld/go-ipld-prime/node/basic"
Expand All @@ -38,14 +35,14 @@ var serial = "\xa4eplainkolde stringcmap\xa2cone\x01ctwo\x02dlist\x82ethreedfour
func TestRoundtrip(t *testing.T) {
t.Run("encoding", func(t *testing.T) {
var buf bytes.Buffer
err := Encoder(n, &buf)
err := Encode(n, &buf)
Require(t, err, ShouldEqual, nil)
Wish(t, buf.String(), ShouldEqual, serial)
})
t.Run("decoding", func(t *testing.T) {
buf := strings.NewReader(serial)
nb := basicnode.Prototype__Map{}.NewBuilder()
err := Decoder(nb, buf)
err := Decode(nb, buf)
Require(t, err, ShouldEqual, nil)
Wish(t, nb.Build(), ShouldEqual, n)
})
Expand All @@ -57,33 +54,26 @@ func TestRoundtripScalar(t *testing.T) {
simple := nb.Build()
t.Run("encoding", func(t *testing.T) {
var buf bytes.Buffer
err := Encoder(simple, &buf)
err := Encode(simple, &buf)
Require(t, err, ShouldEqual, nil)
Wish(t, buf.String(), ShouldEqual, `japplesauce`)
})
t.Run("decoding", func(t *testing.T) {
buf := strings.NewReader(`japplesauce`)
nb := basicnode.Prototype__String{}.NewBuilder()
err := Decoder(nb, buf)
err := Decode(nb, buf)
Require(t, err, ShouldEqual, nil)
Wish(t, nb.Build(), ShouldEqual, simple)
})
}

func TestRoundtripLinksAndBytes(t *testing.T) {
lb := cidlink.LinkBuilder{cid.Prefix{
lnk := cidlink.LinkPrototype{cid.Prefix{
Version: 1,
Codec: 0x71,
MhType: 0x17,
MhType: 0x13,
MhLength: 4,
}}
buf := bytes.Buffer{}
lnk, err := lb.Build(context.Background(), ipld.LinkContext{}, n,
func(ipld.LinkContext) (io.Writer, ipld.StoreCommitter, error) {
return &buf, func(lnk ipld.Link) error { return nil }, nil
},
)
Require(t, err, ShouldEqual, nil)
}}.BuildLink([]byte{1, 2, 3, 4}) // dummy value, content does not matter to this test.

var linkByteNode = fluent.MustBuildMap(basicnode.Prototype__Map{}, 4, func(na fluent.MapAssembler) {
nva := na.AssembleEntry("Link")
Expand All @@ -94,11 +84,11 @@ func TestRoundtripLinksAndBytes(t *testing.T) {
nva.AssignBytes(bytes)
})

buf.Reset()
err = Encoder(linkByteNode, &buf)
buf := bytes.Buffer{}
err := Encode(linkByteNode, &buf)
Require(t, err, ShouldEqual, nil)
nb := basicnode.Prototype__Map{}.NewBuilder()
err = Decoder(nb, &buf)
err = Decode(nb, &buf)
Require(t, err, ShouldEqual, nil)
reconstructed := nb.Build()
Wish(t, reconstructed, ShouldEqual, linkByteNode)
Expand Down
8 changes: 4 additions & 4 deletions codec/dagcbor/unmarshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,28 @@ func TestFunBlocks(t *testing.T) {
// This fixture has a zero length link -- not even the multibase byte (which dag-cbor insists must be zero) is there.
buf := strings.NewReader("\x8d\x8d\x97\xd8*@")
nb := basicnode.Prototype.Any.NewBuilder()
err := Decoder(nb, buf)
err := Decode(nb, buf)
Require(t, err, ShouldEqual, ErrInvalidMultibase)
})
t.Run("fuzz001", func(t *testing.T) {
// This fixture might cause an overly large allocation if you aren't careful to have resource budgets.
buf := strings.NewReader("\x9a\xff000")
nb := basicnode.Prototype.Any.NewBuilder()
err := Decoder(nb, buf)
err := Decode(nb, buf)
Require(t, err, ShouldEqual, ErrAllocationBudgetExceeded)
})
t.Run("fuzz002", func(t *testing.T) {
// This fixture might cause an overly large allocation if you aren't careful to have resource budgets.
buf := strings.NewReader("\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9f\x9a\xff000")
nb := basicnode.Prototype.Any.NewBuilder()
err := Decoder(nb, buf)
err := Decode(nb, buf)
Require(t, err, ShouldEqual, ErrAllocationBudgetExceeded)
})
t.Run("fuzz003", func(t *testing.T) {
// This fixture might cause an overly large allocation if you aren't careful to have resource budgets.
buf := strings.NewReader("\x9f\x9f\x9f\x9f\x9f\x9f\x9f\xbb00000000")
nb := basicnode.Prototype.Any.NewBuilder()
err := Decoder(nb, buf)
err := Decode(nb, buf)
Require(t, err, ShouldEqual, ErrAllocationBudgetExceeded)
})
}
16 changes: 8 additions & 8 deletions codec/dagjson/multicodec.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ import (

"github.com/polydawn/refmt/json"

ipld "github.com/ipld/go-ipld-prime"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/multicodec"
)

var (
_ cidlink.MulticodecDecoder = Decoder
_ cidlink.MulticodecEncoder = Encoder
_ ipld.Decoder = Decode
_ ipld.Encoder = Encode
)

func init() {
cidlink.RegisterMulticodecDecoder(0x0129, Decoder)
cidlink.RegisterMulticodecEncoder(0x0129, Encoder)
multicodec.EncoderRegistry[0x0129] = Encode
multicodec.DecoderRegistry[0x0129] = Decode
}

func Decoder(na ipld.NodeAssembler, r io.Reader) error {
func Decode(na ipld.NodeAssembler, r io.Reader) error {
// Shell out directly to generic builder path.
// (There's not really any fastpaths of note for json.)
err := Unmarshal(na, json.NewDecoder(r))
Expand Down Expand Up @@ -52,7 +52,7 @@ func Decoder(na ipld.NodeAssembler, r io.Reader) error {
return err
}

func Encoder(n ipld.Node, w io.Writer) error {
func Encode(n ipld.Node, w io.Writer) error {
// Shell out directly to generic inspection path.
// (There's not really any fastpaths of note for json.)
// Write another function if you need to tune encoding options about whitespace.
Expand Down
44 changes: 18 additions & 26 deletions codec/dagjson/roundtripCidlink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package dagjson

import (
"bytes"
"context"
"io"
"io/ioutil"
"strings"
"testing"

Expand All @@ -17,29 +15,28 @@ import (
)

func TestRoundtripCidlink(t *testing.T) {
lb := cidlink.LinkBuilder{cid.Prefix{
lp := cidlink.LinkPrototype{cid.Prefix{
Version: 1,
Codec: 0x0129,
MhType: 0x17,
MhType: 0x13,
MhLength: 4,
}}
lsys := cidlink.DefaultLinkSystem()

buf := bytes.Buffer{}
lnk, err := lb.Build(context.Background(), ipld.LinkContext{}, n,
func(ipld.LinkContext) (io.Writer, ipld.StoreCommitter, error) {
return &buf, func(lnk ipld.Link) error { return nil }, nil
},
)
lsys.StorageWriteOpener = func(lnkCtx ipld.LinkContext) (io.Writer, ipld.BlockWriteCommitter, error) {
return &buf, func(lnk ipld.Link) error { return nil }, nil
}
lsys.StorageReadOpener = func(lnkCtx ipld.LinkContext, lnk ipld.Link) (io.Reader, error) {
return bytes.NewReader(buf.Bytes()), nil
}

lnk, err := lsys.Store(ipld.LinkContext{}, lp, n)
Require(t, err, ShouldEqual, nil)

nb := basicnode.Prototype__Any{}.NewBuilder()
err = lnk.Load(context.Background(), ipld.LinkContext{}, nb,
func(lnk ipld.Link, _ ipld.LinkContext) (io.Reader, error) {
return bytes.NewReader(buf.Bytes()), nil
},
)
n2, err := lsys.Load(ipld.LinkContext{}, lnk, basicnode.Prototype.Any)
Require(t, err, ShouldEqual, nil)
Wish(t, nb.Build(), ShouldEqual, n)
Wish(t, n2, ShouldEqual, n)
}

// Make sure that a map that *almost* looks like a link is handled safely.
Expand All @@ -48,24 +45,19 @@ func TestRoundtripCidlink(t *testing.T) {
// tokens have to be reprocessed before a recursion that find a real link appears.
func TestUnmarshalTrickyMapContainingLink(t *testing.T) {
// Create a link; don't particularly care about its contents.
lnk, err := cidlink.LinkBuilder{cid.Prefix{
lnk := cidlink.LinkPrototype{cid.Prefix{
Version: 1,
Codec: 0x0129,
MhType: 0x17,
Codec: 0x71,
MhType: 0x13,
MhLength: 4,
}}.Build(context.Background(), ipld.LinkContext{}, n,
func(ipld.LinkContext) (io.Writer, ipld.StoreCommitter, error) {
return ioutil.Discard, func(lnk ipld.Link) error { return nil }, nil
},
)
Require(t, err, ShouldEqual, nil)
}}.BuildLink([]byte{1, 2, 3, 4}) // dummy value, content does not matter to this test.

// Compose the tricky corpus. (lnk.String "happens" to work here, although this isn't recommended or correct in general.)
tricky := `{"/":{"/":"` + lnk.String() + `"}}`

// Unmarshal. Hopefully we get a map with a link in it.
nb := basicnode.Prototype__Any{}.NewBuilder()
err = Decoder(nb, strings.NewReader(tricky))
err := Decode(nb, strings.NewReader(tricky))
Require(t, err, ShouldEqual, nil)
n := nb.Build()
Wish(t, n.Kind(), ShouldEqual, ipld.Kind_Map)
Expand Down
Loading