marshal.go 4.16 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
	}
Daniel Martí's avatar
Daniel Martí committed
39
	var enc []byte
Rod Vagg's avatar
Rod Vagg committed
40 41 42
	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
43

Rod Vagg's avatar
Rod Vagg committed
44 45 46
		linksIter := links.ListIterator()
		for !linksIter.Done() {
			ii, link, err := linksIter.Next()
Rod Vagg's avatar
Rod Vagg committed
47
			if err != nil {
Rod Vagg's avatar
Rod Vagg committed
48
				return err
Rod Vagg's avatar
Rod Vagg committed
49 50
			}

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

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

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

Rod Vagg's avatar
Rod Vagg committed
107 108
		// 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
109
		sort.Stable(pbLinkSlice(pbLinks))
Rod Vagg's avatar
Rod Vagg committed
110
		for _, link := range pbLinks {
Daniel Martí's avatar
Daniel Martí committed
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
			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
133
			}
Daniel Martí's avatar
Daniel Martí committed
134 135 136
			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
137
			}
Rod Vagg's avatar
Rod Vagg committed
138 139 140
		}
	} // if links

Rod Vagg's avatar
Rod Vagg committed
141
	// Data (optional)
Rod Vagg's avatar
Rod Vagg committed
142 143 144 145 146 147 148 149
	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
150
		}
Daniel Martí's avatar
Daniel Martí committed
151 152
		enc = protowire.AppendTag(enc, 1, 2) // field & wire type for Data
		enc = protowire.AppendBytes(enc, byts)
Rod Vagg's avatar
Rod Vagg committed
153 154
	}

Daniel Martí's avatar
Daniel Martí committed
155 156
	_, err = out.Write(enc)
	return err
Rod Vagg's avatar
Rod Vagg committed
157 158 159 160 161 162 163 164 165 166 167
}

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
}