marshal.go 4.81 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

8 9 10
	"gitlab.dms3.io/dms3/go-cid"
	ld "gitlab.dms3.io/ld/go-ld-prime"
	cidlink "gitlab.dms3.io/ld/go-ld-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
}

22 23
// Encode provides an LD codec encode interface for DAG-PB data. Provide a
// conforming Node and a destination for bytes to marshal a DAG-PB LD Node.
Rod Vagg's avatar
Rod Vagg committed
24
// The Node must strictly conform to the DAG-PB schema
25
// (https://gitlab.dms3.io/ld/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.
27
// This function is registered via the go-ld-prime link loader for multicodec
28
// code 0x70 when this package is invoked via init.
29
func Encode(node ld.Node, w io.Writer) error {
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
	// 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)

	enc, err := AppendEncode(enc, node)
	if err != nil {
		return err
	}
	_, err = w.Write(enc)
	return err
}

// AppendEncode is like Encode, but it uses a destination buffer directly.
// This means less copying of bytes, and if the destination has enough capacity,
// fewer allocations.
45
func AppendEncode(enc []byte, inNode ld.Node) ([]byte, error) {
Rod Vagg's avatar
Rod Vagg committed
46
	// Wrap in a typed node for some basic schema form checking
Rod Vagg's avatar
Rod Vagg committed
47
	builder := Type.PBNode.NewBuilder()
48
	if err := builder.AssignNode(inNode); err != nil {
49
		return enc, err
Rod Vagg's avatar
Rod Vagg committed
50 51 52
	}
	node := builder.Build()

Rod Vagg's avatar
Rod Vagg committed
53 54
	links, err := node.LookupByString("Links")
	if err != nil {
55
		return enc, err
Rod Vagg's avatar
Rod Vagg committed
56
	}
57

Rod Vagg's avatar
Rod Vagg committed
58 59 60
	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
61

Rod Vagg's avatar
Rod Vagg committed
62 63 64
		linksIter := links.ListIterator()
		for !linksIter.Done() {
			ii, link, err := linksIter.Next()
Rod Vagg's avatar
Rod Vagg committed
65
			if err != nil {
66
				return enc, err
Rod Vagg's avatar
Rod Vagg committed
67 68
			}

Rod Vagg's avatar
Rod Vagg committed
69
			{ // Hash (required)
Rod Vagg's avatar
Rod Vagg committed
70 71
				d, err := link.LookupByString("Hash")
				if err != nil {
72
					return enc, err
Rod Vagg's avatar
Rod Vagg committed
73 74 75
				}
				l, err := d.AsLink()
				if err != nil {
76
					return enc, err
Rod Vagg's avatar
Rod Vagg committed
77
				}
Rod Vagg's avatar
Rod Vagg committed
78
				if err != nil {
79
					return enc, err
Rod Vagg's avatar
Rod Vagg committed
80
				}
Rod Vagg's avatar
Rod Vagg committed
81 82
				cl, ok := l.(cidlink.Link)
				if !ok {
Rod Vagg's avatar
Rod Vagg committed
83 84
					// this _should_ be taken care of by the Typed conversion above with
					// "missing required fields: Hash"
85
					return enc, fmt.Errorf("invalid DAG-PB form (link must have a Hash)")
Rod Vagg's avatar
Rod Vagg committed
86 87
				}
				pbLinks[ii].hash = cl.Cid
Rod Vagg's avatar
Rod Vagg committed
88 89
			}

Rod Vagg's avatar
Rod Vagg committed
90
			{ // Name (optional)
Rod Vagg's avatar
Rod Vagg committed
91 92
				nameNode, err := link.LookupByString("Name")
				if err != nil {
93
					return enc, err
Rod Vagg's avatar
Rod Vagg committed
94 95 96 97
				}
				if !nameNode.IsAbsent() {
					name, err := nameNode.AsString()
					if err != nil {
98
						return enc, err
Rod Vagg's avatar
Rod Vagg committed
99
					}
Rod Vagg's avatar
Rod Vagg committed
100 101
					pbLinks[ii].name = name
					pbLinks[ii].hasName = true
Rod Vagg's avatar
Rod Vagg committed
102 103 104
				}
			}

Rod Vagg's avatar
Rod Vagg committed
105
			{ // Tsize (optional)
Rod Vagg's avatar
Rod Vagg committed
106 107
				tsizeNode, err := link.LookupByString("Tsize")
				if err != nil {
108
					return enc, err
Rod Vagg's avatar
Rod Vagg committed
109 110 111 112
				}
				if !tsizeNode.IsAbsent() {
					tsize, err := tsizeNode.AsInt()
					if err != nil {
113
						return enc, err
Rod Vagg's avatar
Rod Vagg committed
114 115
					}
					if tsize < 0 {
116
						return enc, fmt.Errorf("Link has negative Tsize value [%v]", tsize)
Rod Vagg's avatar
Rod Vagg committed
117
					}
Rod Vagg's avatar
Rod Vagg committed
118 119 120
					utsize := uint64(tsize)
					pbLinks[ii].tsize = utsize
					pbLinks[ii].hasTsize = true
Rod Vagg's avatar
Rod Vagg committed
121 122
				}
			}
Rod Vagg's avatar
Rod Vagg committed
123 124
		} // for

Rod Vagg's avatar
Rod Vagg committed
125 126
		// 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
127
		sort.Stable(pbLinkSlice(pbLinks))
Rod Vagg's avatar
Rod Vagg committed
128
		for _, link := range pbLinks {
Daniel Martí's avatar
Daniel Martí committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
			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
151
			}
Daniel Martí's avatar
Daniel Martí committed
152 153 154
			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
155
			}
Rod Vagg's avatar
Rod Vagg committed
156 157 158
		}
	} // if links

Rod Vagg's avatar
Rod Vagg committed
159
	// Data (optional)
Rod Vagg's avatar
Rod Vagg committed
160 161
	data, err := node.LookupByString("Data")
	if err != nil {
162
		return enc, err
Rod Vagg's avatar
Rod Vagg committed
163 164 165 166
	}
	if !data.IsAbsent() {
		byts, err := data.AsBytes()
		if err != nil {
167
			return enc, err
Rod Vagg's avatar
Rod Vagg committed
168
		}
Daniel Martí's avatar
Daniel Martí committed
169 170
		enc = protowire.AppendTag(enc, 1, 2) // field & wire type for Data
		enc = protowire.AppendBytes(enc, byts)
Rod Vagg's avatar
Rod Vagg committed
171 172
	}

173
	return enc, err
Rod Vagg's avatar
Rod Vagg committed
174 175 176 177 178 179 180 181 182 183 184
}

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
}