cidLink.go 2.75 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
package cidlink

import (
	"bytes"
	"context"
	"fmt"
	"io"

	cid "github.com/ipfs/go-cid"
	ipld "github.com/ipld/go-ipld-prime"
	multihash "github.com/multiformats/go-multihash"
)

var (
	_ ipld.Link        = Link{}
	_ ipld.LinkBuilder = LinkBuilder{}
)

type Link struct {
	cid.Cid
}

func (lnk Link) Load(ctx context.Context, lnkCtx ipld.LinkContext, nb ipld.NodeBuilder, loader ipld.Loader) (ipld.Node, error) {
	// Open the byte reader.
	r, err := loader(lnk, lnkCtx)
	if err != nil {
		return nil, err
	}
	// Tee into hash checking and unmarshalling.
	mcDecoder, exists := multicodecDecodeTable[lnk.Prefix().Codec]
	if !exists {
		return nil, fmt.Errorf("no decoder registered for multicodec %d", lnk.Prefix().Codec)
	}
	var hasher bytes.Buffer // multihash only exports bulk use, which is... really inefficient and should be fixed.
	node, decodeErr := mcDecoder(nb, io.TeeReader(r, &hasher))
	// Error checking order here is tricky.
	//  If decoding errored out, we should still run the reader to the end, to check the hash.
	//  (We still don't implement this by running the hash to the end first, because that would increase the high-water memory requirement.)
	//   ((Which we experience right now anyway because multihash's interface is silly, but we're acting as if that's fixed or will be soon.))
	//  If the hash is rejected, we should return that error (and even if there was a decodeErr, it becomes irrelevant).
	if decodeErr != nil {
		_, err := io.Copy(&hasher, r)
		if err != nil {
			return nil, err
		}
	}
	hash, err := multihash.Sum(hasher.Bytes(), lnk.Prefix().MhType, lnk.Prefix().MhLength)
	if err != nil {
		return nil, err
	}
	if hash.B58String() != lnk.Hash().B58String() {
		return nil, fmt.Errorf("hash mismatch!")
	}
	if decodeErr != nil {
		return nil, decodeErr
	}
	return node, nil
}
func (lnk Link) LinkBuilder() ipld.LinkBuilder {
	return LinkBuilder{lnk.Cid.Prefix()}
}
func (lnk Link) String() string {
	return lnk.Cid.String()
}

type LinkBuilder struct {
	cid.Prefix
}

func (lb LinkBuilder) Build(ctx context.Context, lnkCtx ipld.LinkContext, node ipld.Node, storer ipld.Storer) (ipld.Link, error) {
	// Open the byte writer.
	w, commit, err := storer(lnkCtx)
	if err != nil {
		return nil, err
	}
	// Marshal, teeing into the storage writer and the hasher.
77
	mcEncoder, exists := multicodecEncodeTable[lb.Prefix.Codec]
78
	if !exists {
79
		return nil, fmt.Errorf("no encoder registered for multicodec %d", lb.Prefix.Codec)
80
	}
81
	var hasher bytes.Buffer // multihash-via-cid only exports bulk use, which is... really inefficient and should be fixed.
82 83 84 85 86
	w = io.MultiWriter(&hasher, w)
	err = mcEncoder(node, w)
	if err != nil {
		return nil, err
	}
87 88 89 90
	cid, err := lb.Prefix.Sum(hasher.Bytes())
	lnk := Link{cid}
	if err := commit(lnk); err != nil {
		return lnk, err
91
	}
92
	return lnk, nil
93
}