node.go 8.6 KB
Newer Older
1 2 3
package merkledag

import (
4
	"context"
5
	"encoding/json"
Jeromy's avatar
Jeromy committed
6
	"fmt"
Jeromy's avatar
Jeromy committed
7

Jeromy's avatar
Jeromy committed
8
	mh "github.com/multiformats/go-multihash"
9 10
	cid "gitlab.dms3.io/dms3/go-cid"
	ld "gitlab.dms3.io/dms3/go-ld-format"
11 12
)

13 14 15 16 17
// Common errors
var (
	ErrNotProtobuf  = fmt.Errorf("expected protobuf dag node")
	ErrLinkNotFound = fmt.Errorf("no link by that name")
)
18

19
// ProtoNode represents a node in the DMS3 Merkle DAG.
20
// nodes have opaque data and a set of navigable links.
21
type ProtoNode struct {
22
	links []*ld.Link
23
	data  []byte
24 25 26 27

	// cache encoded/marshaled value
	encoded []byte

28
	cached cid.Cid
29

30 31
	// builder specifies cid version and hashing function
	builder cid.Builder
32 33
}

34
var v0CidPrefix = cid.Prefix{
35 36 37 38
	Codec:    cid.DagProtobuf,
	MhLength: -1,
	MhType:   mh.SHA2_256,
	Version:  0,
39 40
}

41 42 43 44 45 46 47
var v1CidPrefix = cid.Prefix{
	Codec:    cid.DagProtobuf,
	MhLength: -1,
	MhType:   mh.SHA2_256,
	Version:  1,
}

Kevin Atkinson's avatar
Kevin Atkinson committed
48
// V0CidPrefix returns a prefix for CIDv0
49
func V0CidPrefix() cid.Prefix { return v0CidPrefix }
Kevin Atkinson's avatar
Kevin Atkinson committed
50 51

// V1CidPrefix returns a prefix for CIDv1 with the default settings
52 53
func V1CidPrefix() cid.Prefix { return v1CidPrefix }

Kevin Atkinson's avatar
Kevin Atkinson committed
54
// PrefixForCidVersion returns the Protobuf prefix for a given CID version
55 56 57 58 59 60 61 62 63 64 65
func PrefixForCidVersion(version int) (cid.Prefix, error) {
	switch version {
	case 0:
		return v0CidPrefix, nil
	case 1:
		return v1CidPrefix, nil
	default:
		return cid.Prefix{}, fmt.Errorf("unknown CID version: %d", version)
	}
}

66 67
// CidBuilder returns the CID Builder for this ProtoNode, it is never nil
func (n *ProtoNode) CidBuilder() cid.Builder {
68 69 70 71 72 73
	if n.builder == nil {
		n.builder = v0CidPrefix
	}
	return n.builder
}

74 75 76 77
// SetCidBuilder sets the CID builder if it is non nil, if nil then it
// is reset to the default value
func (n *ProtoNode) SetCidBuilder(builder cid.Builder) {
	if builder == nil {
78
		n.builder = v0CidPrefix
79
	} else {
80
		n.builder = builder.WithCodec(cid.DagProtobuf)
81
		n.cached = cid.Undef
82
	}
83 84
}

85 86
// LinkSlice is a slice of ld.Links
type LinkSlice []*ld.Link
87 88 89 90 91

func (ls LinkSlice) Len() int           { return len(ls) }
func (ls LinkSlice) Swap(a, b int)      { ls[a], ls[b] = ls[b], ls[a] }
func (ls LinkSlice) Less(a, b int) bool { return ls[a].Name < ls[b].Name }

92
// NodeWithData builds a new Protonode with the given data.
93 94
func NodeWithData(d []byte) *ProtoNode {
	return &ProtoNode{data: d}
95 96
}

97
// AddNodeLink adds a link to another node.
98
func (n *ProtoNode) AddNodeLink(name string, that ld.Node) error {
99
	n.encoded = nil
100

101
	lnk, err := ld.MakeLink(that)
102 103 104 105
	if err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
106 107
	lnk.Name = name

108 109
	n.AddRawLink(name, lnk)

110 111 112
	return nil
}

113
// AddRawLink adds a copy of a link to this node
114
func (n *ProtoNode) AddRawLink(name string, l *ld.Link) error {
115
	n.encoded = nil
116
	n.links = append(n.links, &ld.Link{
117 118
		Name: name,
		Size: l.Size,
119
		Cid:  l.Cid,
120
	})
121 122 123 124

	return nil
}

Steven Allen's avatar
Steven Allen committed
125
// RemoveNodeLink removes a link on this node by the given name.
126
func (n *ProtoNode) RemoveNodeLink(name string) error {
127
	n.encoded = nil
Jeromy's avatar
Jeromy committed
128

129
	ref := n.links[:0]
130 131
	found := false

132 133 134
	for _, v := range n.links {
		if v.Name != name {
			ref = append(ref, v)
Jeromy's avatar
Jeromy committed
135 136
		} else {
			found = true
137 138
		}
	}
Jeromy's avatar
Jeromy committed
139 140

	if !found {
141
		return ErrLinkNotFound
Jeromy's avatar
Jeromy committed
142 143
	}

144
	n.links = ref
145

Jeromy's avatar
Jeromy committed
146
	return nil
147 148
}

Steven Allen's avatar
Steven Allen committed
149
// GetNodeLink returns a copy of the link with the given name.
150
func (n *ProtoNode) GetNodeLink(name string) (*ld.Link, error) {
151
	for _, l := range n.links {
152
		if l.Name == name {
153
			return &ld.Link{
154 155
				Name: l.Name,
				Size: l.Size,
156
				Cid:  l.Cid,
157 158 159
			}, nil
		}
	}
160
	return nil, ErrLinkNotFound
161 162
}

Steven Allen's avatar
Steven Allen committed
163
// GetLinkedProtoNode returns a copy of the ProtoNode with the given name.
164
func (n *ProtoNode) GetLinkedProtoNode(ctx context.Context, ds ld.DAGService, name string) (*ProtoNode, error) {
165 166 167 168 169 170 171 172 173 174 175 176 177
	nd, err := n.GetLinkedNode(ctx, ds, name)
	if err != nil {
		return nil, err
	}

	pbnd, ok := nd.(*ProtoNode)
	if !ok {
		return nil, ErrNotProtobuf
	}

	return pbnd, nil
}

178 179
// GetLinkedNode returns a copy of the LD Node with the given name.
func (n *ProtoNode) GetLinkedNode(ctx context.Context, ds ld.DAGService, name string) (ld.Node, error) {
180 181 182 183 184 185 186 187
	lnk, err := n.GetNodeLink(name)
	if err != nil {
		return nil, err
	}

	return lnk.GetNode(ctx, ds)
}

188
// Copy returns a copy of the node.
189
// NOTE: Does not make copies of Node objects in the links.
190
func (n *ProtoNode) Copy() ld.Node {
191
	nnode := new(ProtoNode)
192 193 194
	if len(n.data) > 0 {
		nnode.data = make([]byte, len(n.data))
		copy(nnode.data, n.data)
Jeromy's avatar
Jeromy committed
195
	}
196

197
	if len(n.links) > 0 {
198
		nnode.links = make([]*ld.Link, len(n.links))
199
		copy(nnode.links, n.links)
Jeromy's avatar
Jeromy committed
200
	}
201

202
	nnode.builder = n.builder
203

204 205 206
	return nnode
}

207
// RawData returns the protobuf-encoded version of the node.
208
func (n *ProtoNode) RawData() []byte {
Jeromy's avatar
Jeromy committed
209 210 211 212
	out, _ := n.EncodeProtobuf(false)
	return out
}

213
// Data returns the data stored by this node.
214
func (n *ProtoNode) Data() []byte {
215 216 217
	return n.data
}

218
// SetData stores data in this nodes.
219
func (n *ProtoNode) SetData(d []byte) {
220
	n.encoded = nil
221
	n.cached = cid.Undef
222 223 224
	n.data = d
}

225 226
// UpdateNodeLink return a copy of the node with the link name set to point to
// that. If a link of the same name existed, it is removed.
227
func (n *ProtoNode) UpdateNodeLink(name string, that *ProtoNode) (*ProtoNode, error) {
228
	newnode := n.Copy().(*ProtoNode)
229 230
	_ = newnode.RemoveNodeLink(name) // ignore error
	err := newnode.AddNodeLink(name, that)
231 232 233
	return newnode, err
}

234 235
// Size returns the total size of the data addressed by node,
// including the total sizes of references.
236
func (n *ProtoNode) Size() (uint64, error) {
237
	b, err := n.EncodeProtobuf(false)
238 239 240 241 242
	if err != nil {
		return 0, err
	}

	s := uint64(len(b))
243
	for _, l := range n.links {
244 245 246 247 248 249
		s += l.Size
	}
	return s, nil
}

// Stat returns statistics on the node.
250
func (n *ProtoNode) Stat() (*ld.NodeStat, error) {
251
	enc, err := n.EncodeProtobuf(false)
252
	if err != nil {
253
		return nil, err
254 255 256 257
	}

	cumSize, err := n.Size()
	if err != nil {
258
		return nil, err
259 260
	}

261
	return &ld.NodeStat{
Jeromy's avatar
Jeromy committed
262
		Hash:           n.Cid().String(),
263
		NumLinks:       len(n.links),
264
		BlockSize:      len(enc),
265 266
		LinksSize:      len(enc) - len(n.data), // includes framing.
		DataSize:       len(n.data),
267 268 269 270
		CumulativeSize: int(cumSize),
	}, nil
}

271
// Loggable implements the dms3/go-log.Loggable interface.
272
func (n *ProtoNode) Loggable() map[string]interface{} {
Jeromy's avatar
Jeromy committed
273 274 275 276 277
	return map[string]interface{}{
		"node": n.String(),
	}
}

278
// UnmarshalJSON reads the node fields from a JSON-encoded byte slice.
Jeromy's avatar
Jeromy committed
279 280
func (n *ProtoNode) UnmarshalJSON(b []byte) error {
	s := struct {
281 282
		Data  []byte     `json:"data"`
		Links []*ld.Link `json:"links"`
Jeromy's avatar
Jeromy committed
283 284 285 286 287 288 289 290 291 292 293 294
	}{}

	err := json.Unmarshal(b, &s)
	if err != nil {
		return err
	}

	n.data = s.Data
	n.links = s.Links
	return nil
}

295
// MarshalJSON returns a JSON representation of the node.
296 297 298 299 300 301 302 303 304
func (n *ProtoNode) MarshalJSON() ([]byte, error) {
	out := map[string]interface{}{
		"data":  n.data,
		"links": n.links,
	}

	return json.Marshal(out)
}

305 306
// Cid returns the node's Cid, calculated according to its prefix
// and raw data contents.
307 308
func (n *ProtoNode) Cid() cid.Cid {
	if n.encoded != nil && n.cached.Defined() {
309 310 311
		return n.cached
	}

312
	c, err := n.builder.Sum(n.RawData())
313 314
	if err != nil {
		// programmer error
315
		err = fmt.Errorf("invalid CID of length %d: %x: %v", len(n.RawData()), n.RawData(), err)
316 317
		panic(err)
	}
Jeromy's avatar
Jeromy committed
318

319 320
	n.cached = c
	return c
Jeromy's avatar
Jeromy committed
321 322
}

323
// String prints the node's Cid.
324
func (n *ProtoNode) String() string {
Jeromy's avatar
Jeromy committed
325 326 327
	return n.Cid().String()
}

328
// Multihash hashes the encoded data of this node.
329
func (n *ProtoNode) Multihash() mh.Multihash {
330
	// NOTE: EncodeProtobuf generates the hash and puts it in n.cached.
331
	_, err := n.EncodeProtobuf(false)
332
	if err != nil {
Jeromy's avatar
Jeromy committed
333 334
		// Note: no possibility exists for an error to be returned through here
		panic(err)
335 336
	}

Jeromy's avatar
Jeromy committed
337
	return n.cached.Hash()
338
}
339

340
// Links returns the node links.
341
func (n *ProtoNode) Links() []*ld.Link {
342 343 344
	return n.links
}

345
// SetLinks replaces the node links with the given ones.
346
func (n *ProtoNode) SetLinks(links []*ld.Link) {
347 348 349
	n.links = links
}

350
// Resolve is an alias for ResolveLink.
351 352 353 354
func (n *ProtoNode) Resolve(path []string) (interface{}, []string, error) {
	return n.ResolveLink(path)
}

355 356 357
// ResolveLink consumes the first element of the path and obtains the link
// corresponding to it from the node. It returns the link
// and the path without the consumed element.
358
func (n *ProtoNode) ResolveLink(path []string) (*ld.Link, []string, error) {
359 360 361 362 363 364 365 366 367 368 369 370
	if len(path) == 0 {
		return nil, nil, fmt.Errorf("end of path, no more links to resolve")
	}

	lnk, err := n.GetNodeLink(path[0])
	if err != nil {
		return nil, nil, err
	}

	return lnk, path[1:], nil
}

371 372 373
// Tree returns the link names of the ProtoNode.
// ProtoNodes are only ever one path deep, so anything different than an empty
// string for p results in nothing. The depth parameter is ignored.
374 375 376 377 378
func (n *ProtoNode) Tree(p string, depth int) []string {
	if p != "" {
		return nil
	}

379 380 381 382 383 384
	out := make([]string, 0, len(n.links))
	for _, lnk := range n.links {
		out = append(out, lnk.Name)
	}
	return out
}