marshal.go 5.1 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 7
	math_bits "math/bits"
	"sort"
Rod Vagg's avatar
Rod Vagg committed
8

Rod Vagg's avatar
Rod Vagg committed
9
	"github.com/ipfs/go-cid"
Rod Vagg's avatar
Rod Vagg committed
10 11 12 13
	ipld "github.com/ipld/go-ipld-prime"
	cidlink "github.com/ipld/go-ipld-prime/linking/cid"
)

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 39 40 41
	links, err := node.LookupByString("Links")
	if err != nil {
		return err
	}
	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
42

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

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

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

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

Rod Vagg's avatar
Rod Vagg committed
106 107
		// links must be strictly sorted by Name before encoding, leaving stable
		// ordering where the names are the same (or absent)
Rod Vagg's avatar
Rod Vagg committed
108 109 110 111
		sortLinks(pbLinks)
		for _, link := range pbLinks {
			size := link.encodedSize()
			chunk := make([]byte, size+sizeOfVarint(uint64(size))+1)
Rod Vagg's avatar
Rod Vagg committed
112
			chunk[0] = 0x12 // field & wire type for Links
Rod Vagg's avatar
Rod Vagg committed
113 114 115 116
			offset := encodeVarint(chunk, 1, uint64(size))
			wrote, err := link.marshal(chunk, offset)
			if err != nil {
				return err
Rod Vagg's avatar
Rod Vagg committed
117
			}
Rod Vagg's avatar
Rod Vagg committed
118
			if wrote != size {
Daniel Martí's avatar
Daniel Martí committed
119
				return fmt.Errorf("bad PBLink marshal, wrote wrong number of bytes")
Rod Vagg's avatar
Rod Vagg committed
120
			}
Rod Vagg's avatar
Rod Vagg committed
121 122 123 124
			out.Write(chunk)
		}
	} // if links

Rod Vagg's avatar
Rod Vagg committed
125
	// Data (optional)
Rod Vagg's avatar
Rod Vagg committed
126 127 128 129 130 131 132 133
	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
134
		}
Rod Vagg's avatar
Rod Vagg committed
135 136
		size := uint64(len(byts))
		lead := make([]byte, sizeOfVarint(size)+1)
Rod Vagg's avatar
Rod Vagg committed
137
		lead[0] = 0xa // field and wireType for Data
Rod Vagg's avatar
Rod Vagg committed
138 139 140 141 142 143 144 145
		encodeVarint(lead, 1, size)
		out.Write(lead)
		out.Write(byts)
	}

	return nil
}

Rod Vagg's avatar
Rod Vagg committed
146
// predict the byte size of the encoded Link
Rod Vagg's avatar
Rod Vagg committed
147 148 149 150 151 152 153 154 155 156 157 158 159
func (link pbLink) encodedSize() (n int) {
	l := link.hash.ByteLen()
	n += 1 + l + sizeOfVarint(uint64(l))
	if link.hasName {
		l = len(link.name)
		n += 1 + l + sizeOfVarint(uint64(l))
	}
	if link.hasTsize {
		n += 1 + sizeOfVarint(uint64(link.tsize))
	}
	return n
}

Rod Vagg's avatar
Rod Vagg committed
160
// encode a Link to PB
Rod Vagg's avatar
Rod Vagg committed
161 162
func (link pbLink) marshal(data []byte, offset int) (int, error) {
	base := offset
Rod Vagg's avatar
Rod Vagg committed
163
	data[offset] = 0xa // field and wireType for Hash
Rod Vagg's avatar
Rod Vagg committed
164 165 166 167 168
	byts := link.hash.Bytes()
	offset = encodeVarint(data, offset+1, uint64(len(byts)))
	copy(data[offset:], byts)
	offset += len(byts)
	if link.hasName {
Rod Vagg's avatar
Rod Vagg committed
169
		data[offset] = 0x12 // field and wireType for Name
Rod Vagg's avatar
Rod Vagg committed
170 171 172
		offset = encodeVarint(data, offset+1, uint64(len(link.name)))
		copy(data[offset:], link.name)
		offset += len(link.name)
Rod Vagg's avatar
Rod Vagg committed
173
	}
Rod Vagg's avatar
Rod Vagg committed
174
	if link.hasTsize {
Rod Vagg's avatar
Rod Vagg committed
175
		data[offset] = 0x18 // field and wireType for Tsize
Rod Vagg's avatar
Rod Vagg committed
176 177 178 179 180
		offset = encodeVarint(data, offset+1, uint64(link.tsize))
	}
	return offset - base, nil
}

Rod Vagg's avatar
Rod Vagg committed
181 182 183 184 185 186
// predict the size of a varint for PB before creating it
func sizeOfVarint(x uint64) (n int) {
	return (math_bits.Len64(x|1) + 6) / 7
}

// encode a varint to a PB chunk
Rod Vagg's avatar
Rod Vagg committed
187 188 189 190 191 192 193 194 195 196
func encodeVarint(data []byte, offset int, v uint64) int {
	for v >= 1<<7 {
		data[offset] = uint8(v&0x7f | 0x80)
		v >>= 7
		offset++
	}
	data[offset] = uint8(v)
	return offset + 1
}

Rod Vagg's avatar
Rod Vagg committed
197
// stable sorting of Links using the strict sorting rules
Rod Vagg's avatar
Rod Vagg committed
198 199 200 201 202 203 204 205 206 207 208 209 210
func sortLinks(links []pbLink) {
	sort.Stable(pbLinkSlice(links))
}

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
}