marshal.go 4.3 KB
Newer Older
Rod Vagg's avatar
Rod Vagg committed
1 2 3
package dagpb

import (
Daniel Martí's avatar
Daniel Martí committed
4
	"fmt"
Rod Vagg's avatar
Rod Vagg committed
5
	"io"
Rod Vagg's avatar
Rod Vagg committed
6
	"sort"
Rod Vagg's avatar
Rod Vagg committed
7

Rod Vagg's avatar
Rod Vagg committed
8
	"github.com/ipfs/go-cid"
Rod Vagg's avatar
Rod Vagg committed
9 10
	ipld "github.com/ipld/go-ipld-prime"
	cidlink "github.com/ipld/go-ipld-prime/linking/cid"
Daniel Martí's avatar
Daniel Martí committed
11
	"google.golang.org/protobuf/encoding/protowire"
Rod Vagg's avatar
Rod Vagg committed
12 13
)

Rod Vagg's avatar
Rod Vagg committed
14 15 16 17 18 19 20 21
type pbLink struct {
	hash     cid.Cid
	name     string
	hasName  bool
	tsize    uint64
	hasTsize bool
}

Rod Vagg's avatar
Rod Vagg committed
22 23 24 25
// Marshal provides an IPLD codec encode interface for DAG-PB data. Provide a
// conforming Node and a destination for bytes to marshal a DAG-PB IPLD Node.
// The Node must strictly conform to the DAG-PB schema
// (https://github.com/ipld/specs/blob/master/block-layer/codecs/dag-pb.md).
Rod Vagg's avatar
Rod Vagg committed
26
// For safest use, build Nodes using the Type.PBNode type.
Rod Vagg's avatar
Rod Vagg committed
27
func Marshal(inNode ipld.Node, out io.Writer) error {
Rod Vagg's avatar
Rod Vagg committed
28
	// Wrap in a typed node for some basic schema form checking
Rod Vagg's avatar
Rod Vagg committed
29
	builder := Type.PBNode.NewBuilder()
30
	if err := builder.AssignNode(inNode); err != nil {
Rod Vagg's avatar
Rod Vagg committed
31 32 33 34
		return err
	}
	node := builder.Build()

Rod Vagg's avatar
Rod Vagg committed
35 36 37 38
	links, err := node.LookupByString("Links")
	if err != nil {
		return err
	}
39 40 41 42 43

	// 1KiB can be allocated on the stack, and covers most small nodes
	// without having to grow the buffer and cause allocations.
	enc := make([]byte, 0, 1024)

Rod Vagg's avatar
Rod Vagg committed
44 45 46
	if links.Length() > 0 {
		// collect links into a slice so we can properly sort for encoding
		pbLinks := make([]pbLink, links.Length())
Rod Vagg's avatar
Rod Vagg committed
47

Rod Vagg's avatar
Rod Vagg committed
48 49 50
		linksIter := links.ListIterator()
		for !linksIter.Done() {
			ii, link, err := linksIter.Next()
Rod Vagg's avatar
Rod Vagg committed
51
			if err != nil {
Rod Vagg's avatar
Rod Vagg committed
52
				return err
Rod Vagg's avatar
Rod Vagg committed
53 54
			}

Rod Vagg's avatar
Rod Vagg committed
55
			{ // Hash (required)
Rod Vagg's avatar
Rod Vagg committed
56 57
				d, err := link.LookupByString("Hash")
				if err != nil {
Rod Vagg's avatar
Rod Vagg committed
58
					return err
Rod Vagg's avatar
Rod Vagg committed
59 60 61
				}
				l, err := d.AsLink()
				if err != nil {
Rod Vagg's avatar
Rod Vagg committed
62
					return err
Rod Vagg's avatar
Rod Vagg committed
63
				}
Rod Vagg's avatar
Rod Vagg committed
64 65
				if err != nil {
					return err
Rod Vagg's avatar
Rod Vagg committed
66
				}
Rod Vagg's avatar
Rod Vagg committed
67 68
				cl, ok := l.(cidlink.Link)
				if !ok {
Rod Vagg's avatar
Rod Vagg committed
69 70
					// this _should_ be taken care of by the Typed conversion above with
					// "missing required fields: Hash"
Daniel Martí's avatar
Daniel Martí committed
71
					return fmt.Errorf("invalid DAG-PB form (link must have a Hash)")
Rod Vagg's avatar
Rod Vagg committed
72 73
				}
				pbLinks[ii].hash = cl.Cid
Rod Vagg's avatar
Rod Vagg committed
74 75
			}

Rod Vagg's avatar
Rod Vagg committed
76
			{ // Name (optional)
Rod Vagg's avatar
Rod Vagg committed
77 78
				nameNode, err := link.LookupByString("Name")
				if err != nil {
Rod Vagg's avatar
Rod Vagg committed
79
					return err
Rod Vagg's avatar
Rod Vagg committed
80 81 82 83
				}
				if !nameNode.IsAbsent() {
					name, err := nameNode.AsString()
					if err != nil {
Rod Vagg's avatar
Rod Vagg committed
84
						return err
Rod Vagg's avatar
Rod Vagg committed
85
					}
Rod Vagg's avatar
Rod Vagg committed
86 87
					pbLinks[ii].name = name
					pbLinks[ii].hasName = true
Rod Vagg's avatar
Rod Vagg committed
88 89 90
				}
			}

Rod Vagg's avatar
Rod Vagg committed
91
			{ // Tsize (optional)
Rod Vagg's avatar
Rod Vagg committed
92 93
				tsizeNode, err := link.LookupByString("Tsize")
				if err != nil {
Rod Vagg's avatar
Rod Vagg committed
94
					return err
Rod Vagg's avatar
Rod Vagg committed
95 96 97 98
				}
				if !tsizeNode.IsAbsent() {
					tsize, err := tsizeNode.AsInt()
					if err != nil {
Rod Vagg's avatar
Rod Vagg committed
99
						return err
Rod Vagg's avatar
Rod Vagg committed
100 101
					}
					if tsize < 0 {
Daniel Martí's avatar
Daniel Martí committed
102
						return fmt.Errorf("Link has negative Tsize value [%v]", tsize)
Rod Vagg's avatar
Rod Vagg committed
103
					}
Rod Vagg's avatar
Rod Vagg committed
104 105 106
					utsize := uint64(tsize)
					pbLinks[ii].tsize = utsize
					pbLinks[ii].hasTsize = true
Rod Vagg's avatar
Rod Vagg committed
107 108
				}
			}
Rod Vagg's avatar
Rod Vagg committed
109 110
		} // for

Rod Vagg's avatar
Rod Vagg committed
111 112
		// links must be strictly sorted by Name before encoding, leaving stable
		// ordering where the names are the same (or absent)
Daniel Martí's avatar
Daniel Martí committed
113
		sort.Stable(pbLinkSlice(pbLinks))
Rod Vagg's avatar
Rod Vagg committed
114
		for _, link := range pbLinks {
Daniel Martí's avatar
Daniel Martí committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
			hash := link.hash.Bytes()

			size := 0
			size += protowire.SizeTag(2)
			size += protowire.SizeBytes(len(hash))
			if link.hasName {
				size += protowire.SizeTag(2)
				size += protowire.SizeBytes(len(link.name))
			}
			if link.hasTsize {
				size += protowire.SizeTag(3)
				size += protowire.SizeVarint(uint64(link.tsize))
			}

			enc = protowire.AppendTag(enc, 2, 2) // field & wire type for Links
			enc = protowire.AppendVarint(enc, uint64(size))

			enc = protowire.AppendTag(enc, 1, 2) // field & wire type for Hash
			enc = protowire.AppendBytes(enc, hash)
			if link.hasName {
				enc = protowire.AppendTag(enc, 2, 2) // field & wire type for Name
				enc = protowire.AppendString(enc, link.name)
Rod Vagg's avatar
Rod Vagg committed
137
			}
Daniel Martí's avatar
Daniel Martí committed
138 139 140
			if link.hasTsize {
				enc = protowire.AppendTag(enc, 3, 0) // field & wire type for Tsize
				enc = protowire.AppendVarint(enc, uint64(link.tsize))
Rod Vagg's avatar
Rod Vagg committed
141
			}
Rod Vagg's avatar
Rod Vagg committed
142 143 144
		}
	} // if links

Rod Vagg's avatar
Rod Vagg committed
145
	// Data (optional)
Rod Vagg's avatar
Rod Vagg committed
146 147 148 149 150 151 152 153
	data, err := node.LookupByString("Data")
	if err != nil {
		return err
	}
	if !data.IsAbsent() {
		byts, err := data.AsBytes()
		if err != nil {
			return err
Rod Vagg's avatar
Rod Vagg committed
154
		}
Daniel Martí's avatar
Daniel Martí committed
155 156
		enc = protowire.AppendTag(enc, 1, 2) // field & wire type for Data
		enc = protowire.AppendBytes(enc, byts)
Rod Vagg's avatar
Rod Vagg committed
157 158
	}

Daniel Martí's avatar
Daniel Martí committed
159 160
	_, err = out.Write(enc)
	return err
Rod Vagg's avatar
Rod Vagg committed
161 162 163 164 165 166 167 168 169 170 171
}

type pbLinkSlice []pbLink

func (ls pbLinkSlice) Len() int           { return len(ls) }
func (ls pbLinkSlice) Swap(a, b int)      { ls[a], ls[b] = ls[b], ls[a] }
func (ls pbLinkSlice) Less(a, b int) bool { return pbLinkLess(ls[a], ls[b]) }

func pbLinkLess(a pbLink, b pbLink) bool {
	return a.name < b.name
}