unmarshal.go 6.51 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"
Daniel Martí's avatar
Daniel Martí committed
6
	"io/ioutil"
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
// ErrIntOverflow is returned a varint overflows during decode, it indicates
// malformed data
Daniel Martí's avatar
Daniel Martí committed
16
var ErrIntOverflow = fmt.Errorf("protobuf: varint overflow")
Rod Vagg's avatar
Rod Vagg committed
17

18 19 20
// Decode provides an IPLD codec decode interface for DAG-PB data. Provide a
// compatible NodeAssembler and a byte source to unmarshal a DAG-PB IPLD Node.
// Use the NodeAssembler from the PBNode type for safest construction
Rod Vagg's avatar
Rod Vagg committed
21
// (Type.PBNode.NewBuilder()). A Map assembler will also work.
22 23 24 25
// This function is registered via the go-ipld-prime link loader for multicodec
// code 0x70 when this package is invoked via init.
func Decode(na ipld.NodeAssembler, in io.Reader) error {
	var src []byte
Daniel Martí's avatar
Daniel Martí committed
26
	if buf, ok := in.(interface{ Bytes() []byte }); ok {
27
		src = buf.Bytes()
Daniel Martí's avatar
Daniel Martí committed
28 29
	} else {
		var err error
30
		src, err = ioutil.ReadAll(in)
Daniel Martí's avatar
Daniel Martí committed
31 32 33 34
		if err != nil {
			return err
		}
	}
35 36 37 38 39 40 41 42
	return DecodeBytes(na, src)
}

// DecodeBytes is like Decode, but it uses an input buffer directly.
// Decode will grab or read all the bytes from an io.Reader anyway, so this can
// save having to copy the bytes or create a bytes.Buffer.
func DecodeBytes(na ipld.NodeAssembler, src []byte) error {
	remaining := src
Daniel Martí's avatar
Daniel Martí committed
43

Rod Vagg's avatar
Rod Vagg committed
44 45 46 47
	ma, err := na.BeginMap(2)
	if err != nil {
		return err
	}
48
	var links ipld.ListAssembler
Rod Vagg's avatar
Rod Vagg committed
49

Rod Vagg's avatar
Rod Vagg committed
50
	haveData := false
51
	haveLinks := false
Rod Vagg's avatar
Rod Vagg committed
52
	for {
Daniel Martí's avatar
Daniel Martí committed
53
		if len(remaining) == 0 {
Rod Vagg's avatar
Rod Vagg committed
54 55 56
			break
		}

Daniel Martí's avatar
Daniel Martí committed
57 58 59
		fieldNum, wireType, n := protowire.ConsumeTag(remaining)
		if n < 0 {
			return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
60
		}
Daniel Martí's avatar
Daniel Martí committed
61 62
		remaining = remaining[n:]

Rod Vagg's avatar
Rod Vagg committed
63
		if wireType != 2 {
Daniel Martí's avatar
Daniel Martí committed
64
			return fmt.Errorf("protobuf: (PBNode) invalid wireType, expected 2, got %d", wireType)
Rod Vagg's avatar
Rod Vagg committed
65 66
		}

67 68 69 70
		// Note that we allow Data and Links to come in either order,
		// since the spec defines that decoding "should" accept either form.
		// This is for backwards compatibility with older IPFS data.

Daniel Martí's avatar
Daniel Martí committed
71 72
		switch fieldNum {
		case 1:
Rod Vagg's avatar
Rod Vagg committed
73
			if haveData {
Daniel Martí's avatar
Daniel Martí committed
74
				return fmt.Errorf("protobuf: (PBNode) duplicate Data section")
Rod Vagg's avatar
Rod Vagg committed
75
			}
Daniel Martí's avatar
Daniel Martí committed
76 77 78 79

			chunk, n := protowire.ConsumeBytes(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
80
			}
Daniel Martí's avatar
Daniel Martí committed
81 82
			remaining = remaining[n:]

83 84 85 86 87 88 89
			if links != nil {
				// Links came before Data.
				// Finish them before we start Data.
				if err := links.Finish(); err != nil {
					return err
				}
				links = nil
Rod Vagg's avatar
Rod Vagg committed
90
			}
91

Rod Vagg's avatar
Rod Vagg committed
92 93 94
			if err := ma.AssembleKey().AssignString("Data"); err != nil {
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
95
			if err := ma.AssembleValue().AssignBytes(chunk); err != nil {
Rod Vagg's avatar
Rod Vagg committed
96 97
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
98
			haveData = true
Daniel Martí's avatar
Daniel Martí committed
99 100 101 102 103

		case 2:
			bytesLen, n := protowire.ConsumeVarint(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
104
			}
Daniel Martí's avatar
Daniel Martí committed
105 106
			remaining = remaining[n:]

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
			if links == nil {
				if haveLinks {
					return fmt.Errorf("protobuf: (PBNode) duplicate Links section")
				}

				// The repeated "Links" part begins.
				if err := ma.AssembleKey().AssignString("Links"); err != nil {
					return err
				}
				links, err = ma.AssembleValue().BeginList(0)
				if err != nil {
					return err
				}
			}

Rod Vagg's avatar
Rod Vagg committed
122
			curLink, err := links.AssembleValue().BeginMap(3)
Rod Vagg's avatar
Rod Vagg committed
123 124 125
			if err != nil {
				return err
			}
Daniel Martí's avatar
Daniel Martí committed
126
			if err := unmarshalLink(remaining[:bytesLen], curLink); err != nil {
Rod Vagg's avatar
Rod Vagg committed
127 128
				return err
			}
Daniel Martí's avatar
Daniel Martí committed
129
			remaining = remaining[bytesLen:]
Rod Vagg's avatar
Rod Vagg committed
130
			if err := curLink.Finish(); err != nil {
Rod Vagg's avatar
Rod Vagg committed
131 132
				return err
			}
133
			haveLinks = true
Daniel Martí's avatar
Daniel Martí committed
134 135

		default:
Daniel Martí's avatar
Daniel Martí committed
136
			return fmt.Errorf("protobuf: (PBNode) invalid fieldNumber, expected 1 or 2, got %d", fieldNum)
Rod Vagg's avatar
Rod Vagg committed
137 138 139 140
		}
	}

	if links != nil {
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
		// We had some links at the end, so finish them.
		if err := links.Finish(); err != nil {
			return err
		}

	} else if !haveLinks {
		// We didn't have any links.
		// Since we always want a Links field, add one here.
		if err := ma.AssembleKey().AssignString("Links"); err != nil {
			return err
		}
		links, err := ma.AssembleValue().BeginList(0)
		if err != nil {
			return err
		}
Rod Vagg's avatar
Rod Vagg committed
156 157 158 159 160 161 162
		if err := links.Finish(); err != nil {
			return err
		}
	}
	return ma.Finish()
}

Daniel Martí's avatar
Daniel Martí committed
163
func unmarshalLink(remaining []byte, ma ipld.MapAssembler) error {
Rod Vagg's avatar
Rod Vagg committed
164 165 166 167
	haveHash := false
	haveName := false
	haveTsize := false
	for {
Daniel Martí's avatar
Daniel Martí committed
168
		if len(remaining) == 0 {
Rod Vagg's avatar
Rod Vagg committed
169 170
			break
		}
Daniel Martí's avatar
Daniel Martí committed
171 172 173 174

		fieldNum, wireType, n := protowire.ConsumeTag(remaining)
		if n < 0 {
			return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
175
		}
Daniel Martí's avatar
Daniel Martí committed
176
		remaining = remaining[n:]
Rod Vagg's avatar
Rod Vagg committed
177

Daniel Martí's avatar
Daniel Martí committed
178 179
		switch fieldNum {
		case 1:
Rod Vagg's avatar
Rod Vagg committed
180
			if haveHash {
Daniel Martí's avatar
Daniel Martí committed
181
				return fmt.Errorf("protobuf: (PBLink) duplicate Hash section")
Rod Vagg's avatar
Rod Vagg committed
182 183
			}
			if haveName {
Daniel Martí's avatar
Daniel Martí committed
184
				return fmt.Errorf("protobuf: (PBLink) invalid order, found Name before Hash")
Rod Vagg's avatar
Rod Vagg committed
185 186
			}
			if haveTsize {
Daniel Martí's avatar
Daniel Martí committed
187
				return fmt.Errorf("protobuf: (PBLink) invalid order, found Tsize before Hash")
Rod Vagg's avatar
Rod Vagg committed
188 189
			}
			if wireType != 2 {
Daniel Martí's avatar
Daniel Martí committed
190
				return fmt.Errorf("protobuf: (PBLink) wrong wireType (%d) for Hash", wireType)
Rod Vagg's avatar
Rod Vagg committed
191 192
			}

Daniel Martí's avatar
Daniel Martí committed
193 194 195
			chunk, n := protowire.ConsumeBytes(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
196
			}
Daniel Martí's avatar
Daniel Martí committed
197 198 199 200
			remaining = remaining[n:]

			_, c, err := cid.CidFromBytes(chunk)
			if err != nil {
Daniel Martí's avatar
Daniel Martí committed
201
				return fmt.Errorf("invalid Hash field found in link, expected CID (%v)", err)
Rod Vagg's avatar
Rod Vagg committed
202 203
			}
			if err := ma.AssembleKey().AssignString("Hash"); err != nil {
Rod Vagg's avatar
Rod Vagg committed
204 205
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
206
			if err := ma.AssembleValue().AssignLink(cidlink.Link{Cid: c}); err != nil {
Rod Vagg's avatar
Rod Vagg committed
207 208
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
209
			haveHash = true
Daniel Martí's avatar
Daniel Martí committed
210 211

		case 2:
Rod Vagg's avatar
Rod Vagg committed
212
			if haveName {
Daniel Martí's avatar
Daniel Martí committed
213
				return fmt.Errorf("protobuf: (PBLink) duplicate Name section")
Rod Vagg's avatar
Rod Vagg committed
214 215
			}
			if haveTsize {
Daniel Martí's avatar
Daniel Martí committed
216
				return fmt.Errorf("protobuf: (PBLink) invalid order, found Tsize before Name")
Rod Vagg's avatar
Rod Vagg committed
217 218
			}
			if wireType != 2 {
Daniel Martí's avatar
Daniel Martí committed
219
				return fmt.Errorf("protobuf: (PBLink) wrong wireType (%d) for Name", wireType)
Rod Vagg's avatar
Rod Vagg committed
220 221
			}

Daniel Martí's avatar
Daniel Martí committed
222 223 224
			chunk, n := protowire.ConsumeBytes(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
225
			}
Daniel Martí's avatar
Daniel Martí committed
226 227
			remaining = remaining[n:]

Rod Vagg's avatar
Rod Vagg committed
228 229 230 231 232 233 234
			if err := ma.AssembleKey().AssignString("Name"); err != nil {
				return err
			}
			if err := ma.AssembleValue().AssignString(string(chunk)); err != nil {
				return err
			}
			haveName = true
Daniel Martí's avatar
Daniel Martí committed
235 236

		case 3:
Rod Vagg's avatar
Rod Vagg committed
237
			if haveTsize {
Daniel Martí's avatar
Daniel Martí committed
238
				return fmt.Errorf("protobuf: (PBLink) duplicate Tsize section")
Rod Vagg's avatar
Rod Vagg committed
239 240
			}
			if wireType != 0 {
Daniel Martí's avatar
Daniel Martí committed
241
				return fmt.Errorf("protobuf: (PBLink) wrong wireType (%d) for Tsize", wireType)
Rod Vagg's avatar
Rod Vagg committed
242 243
			}

Daniel Martí's avatar
Daniel Martí committed
244 245 246
			v, n := protowire.ConsumeVarint(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
247
			}
Daniel Martí's avatar
Daniel Martí committed
248 249
			remaining = remaining[n:]

Rod Vagg's avatar
Rod Vagg committed
250
			if err := ma.AssembleKey().AssignString("Tsize"); err != nil {
Rod Vagg's avatar
Rod Vagg committed
251 252
				return err
			}
253
			if err := ma.AssembleValue().AssignInt(int64(v)); err != nil {
Rod Vagg's avatar
Rod Vagg committed
254 255 256
				return err
			}
			haveTsize = true
Daniel Martí's avatar
Daniel Martí committed
257 258

		default:
Daniel Martí's avatar
Daniel Martí committed
259
			return fmt.Errorf("protobuf: (PBLink) invalid fieldNumber, expected 1, 2 or 3, got %d", fieldNum)
Rod Vagg's avatar
Rod Vagg committed
260 261 262
		}
	}

Rod Vagg's avatar
Rod Vagg committed
263
	if !haveHash {
Daniel Martí's avatar
Daniel Martí committed
264
		return fmt.Errorf("invalid Hash field found in link, expected CID")
Rod Vagg's avatar
Rod Vagg committed
265
	}
Rod Vagg's avatar
Rod Vagg committed
266 267 268

	return nil
}