forked from ipld/go-ipld-prime
/
cidLink.go
107 lines (98 loc) · 3.2 KB
/
cidLink.go
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package cidlink
import (
"bytes"
"context"
"fmt"
"io"
cid "github.com/ipfs/go-cid"
ipld "github.com/ipld/go-ipld-prime"
)
var (
_ ipld.Link = Link{}
_ ipld.LinkBuilder = LinkBuilder{}
)
// Link implements the ipld.Link interface using a CID.
// See https://github.com/ipfs/go-cid for more information about CIDs.
//
// When using this value, typically you'll use it as `Link`, and not `*Link`.
// This includes when handling the value as an `ipld.Link` interface -- the non-pointer form is typically preferable.
// This is because the ipld.Link inteface is often desirable to be able to use as a golang map key,
// and in that context, pointers would not result in the desired behavior.
type Link struct {
cid.Cid
}
// byteAccessor is a reader interface that can access underlying bytes
type byteAccesor interface {
Bytes() []byte
}
func (lnk Link) Load(ctx context.Context, lnkCtx ipld.LinkContext, na ipld.NodeAssembler, loader ipld.Loader) error {
// Open the byte reader.
r, err := loader(lnk, lnkCtx)
if err != nil {
return err
}
// Tee into hash checking and unmarshalling.
mcDecoder, exists := multicodecDecodeTable[lnk.Prefix().Codec]
if !exists {
return fmt.Errorf("no decoder registered for multicodec %d", lnk.Prefix().Codec)
}
var decodeErr error
_, ok := r.(byteAccesor)
if ok {
decodeErr = mcDecoder(na, r)
} else {
var hasher bytes.Buffer // multihash only exports bulk use, which is... really inefficient and should be fixed.
decodeErr = mcDecoder(na, 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 err
}
}
}
if decodeErr != nil {
return decodeErr
}
return 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.
mcEncoder, exists := multicodecEncodeTable[lb.Prefix.Codec]
if !exists {
return nil, fmt.Errorf("no encoder registered for multicodec %d", lb.Prefix.Codec)
}
var hasher bytes.Buffer // multihash-via-cid only exports bulk use, which is... really inefficient and should be fixed.
w = io.MultiWriter(&hasher, w)
err = mcEncoder(node, w)
if err != nil {
return nil, err
}
cid, err := lb.Prefix.Sum(hasher.Bytes())
if err != nil {
return nil, err
}
lnk := Link{cid}
if err := commit(lnk); err != nil {
return lnk, err
}
return lnk, nil
}