unmarshal.go 5.84 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
	}
Rod Vagg's avatar
Rod Vagg committed
48
	// always make "Links", even if we don't use it
Daniel Martí's avatar
Daniel Martí committed
49
	if err := ma.AssembleKey().AssignString("Links"); err != nil {
Rod Vagg's avatar
Rod Vagg committed
50 51 52 53 54 55 56
		return err
	}
	links, err := ma.AssembleValue().BeginList(0)
	if err != nil {
		return err
	}

Rod Vagg's avatar
Rod Vagg committed
57 58
	haveData := false
	for {
Daniel Martí's avatar
Daniel Martí committed
59
		if len(remaining) == 0 {
Rod Vagg's avatar
Rod Vagg committed
60 61 62
			break
		}

Daniel Martí's avatar
Daniel Martí committed
63 64 65
		fieldNum, wireType, n := protowire.ConsumeTag(remaining)
		if n < 0 {
			return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
66
		}
Daniel Martí's avatar
Daniel Martí committed
67 68
		remaining = remaining[n:]

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

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

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

Rod Vagg's avatar
Rod Vagg committed
85 86
			// Data must come after Links, so it's safe to close this here even if we
			// didn't use it
Rod Vagg's avatar
Rod Vagg committed
87 88 89 90 91 92 93
			if err := links.Finish(); err != nil {
				return err
			}
			links = nil
			if err := ma.AssembleKey().AssignString("Data"); err != nil {
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
94
			if err := ma.AssembleValue().AssignBytes(chunk); err != nil {
Rod Vagg's avatar
Rod Vagg committed
95 96
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
97
			haveData = true
Daniel Martí's avatar
Daniel Martí committed
98 99

		case 2:
Rod Vagg's avatar
Rod Vagg committed
100
			if haveData {
Daniel Martí's avatar
Daniel Martí committed
101
				return fmt.Errorf("protobuf: (PBNode) invalid order, found Data before Links content")
Rod Vagg's avatar
Rod Vagg committed
102 103
			}

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

Rod Vagg's avatar
Rod Vagg committed
110
			curLink, err := links.AssembleValue().BeginMap(3)
Rod Vagg's avatar
Rod Vagg committed
111 112 113
			if err != nil {
				return err
			}
Daniel Martí's avatar
Daniel Martí committed
114
			if err := unmarshalLink(remaining[:bytesLen], curLink); err != nil {
Rod Vagg's avatar
Rod Vagg committed
115 116
				return err
			}
Daniel Martí's avatar
Daniel Martí committed
117
			remaining = remaining[bytesLen:]
Rod Vagg's avatar
Rod Vagg committed
118
			if err := curLink.Finish(); err != nil {
Rod Vagg's avatar
Rod Vagg committed
119 120
				return err
			}
Daniel Martí's avatar
Daniel Martí committed
121 122

		default:
Daniel Martí's avatar
Daniel Martí committed
123
			return fmt.Errorf("protobuf: (PBNode) invalid fieldNumber, expected 1 or 2, got %d", fieldNum)
Rod Vagg's avatar
Rod Vagg committed
124 125 126 127 128 129 130 131 132 133 134
		}
	}

	if links != nil {
		if err := links.Finish(); err != nil {
			return err
		}
	}
	return ma.Finish()
}

Daniel Martí's avatar
Daniel Martí committed
135
func unmarshalLink(remaining []byte, ma ipld.MapAssembler) error {
Rod Vagg's avatar
Rod Vagg committed
136 137 138 139
	haveHash := false
	haveName := false
	haveTsize := false
	for {
Daniel Martí's avatar
Daniel Martí committed
140
		if len(remaining) == 0 {
Rod Vagg's avatar
Rod Vagg committed
141 142
			break
		}
Daniel Martí's avatar
Daniel Martí committed
143 144 145 146

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

Daniel Martí's avatar
Daniel Martí committed
150 151
		switch fieldNum {
		case 1:
Rod Vagg's avatar
Rod Vagg committed
152
			if haveHash {
Daniel Martí's avatar
Daniel Martí committed
153
				return fmt.Errorf("protobuf: (PBLink) duplicate Hash section")
Rod Vagg's avatar
Rod Vagg committed
154 155
			}
			if haveName {
Daniel Martí's avatar
Daniel Martí committed
156
				return fmt.Errorf("protobuf: (PBLink) invalid order, found Name before Hash")
Rod Vagg's avatar
Rod Vagg committed
157 158
			}
			if haveTsize {
Daniel Martí's avatar
Daniel Martí committed
159
				return fmt.Errorf("protobuf: (PBLink) invalid order, found Tsize before Hash")
Rod Vagg's avatar
Rod Vagg committed
160 161
			}
			if wireType != 2 {
Daniel Martí's avatar
Daniel Martí committed
162
				return fmt.Errorf("protobuf: (PBLink) wrong wireType (%d) for Hash", wireType)
Rod Vagg's avatar
Rod Vagg committed
163 164
			}

Daniel Martí's avatar
Daniel Martí committed
165 166 167
			chunk, n := protowire.ConsumeBytes(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
168
			}
Daniel Martí's avatar
Daniel Martí committed
169 170 171 172
			remaining = remaining[n:]

			_, c, err := cid.CidFromBytes(chunk)
			if err != nil {
Daniel Martí's avatar
Daniel Martí committed
173
				return fmt.Errorf("invalid Hash field found in link, expected CID (%v)", err)
Rod Vagg's avatar
Rod Vagg committed
174 175
			}
			if err := ma.AssembleKey().AssignString("Hash"); err != nil {
Rod Vagg's avatar
Rod Vagg committed
176 177
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
178
			if err := ma.AssembleValue().AssignLink(cidlink.Link{Cid: c}); err != nil {
Rod Vagg's avatar
Rod Vagg committed
179 180
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
181
			haveHash = true
Daniel Martí's avatar
Daniel Martí committed
182 183

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

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

Rod Vagg's avatar
Rod Vagg committed
200 201 202 203 204 205 206
			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
207 208

		case 3:
Rod Vagg's avatar
Rod Vagg committed
209
			if haveTsize {
Daniel Martí's avatar
Daniel Martí committed
210
				return fmt.Errorf("protobuf: (PBLink) duplicate Tsize section")
Rod Vagg's avatar
Rod Vagg committed
211 212
			}
			if wireType != 0 {
Daniel Martí's avatar
Daniel Martí committed
213
				return fmt.Errorf("protobuf: (PBLink) wrong wireType (%d) for Tsize", wireType)
Rod Vagg's avatar
Rod Vagg committed
214 215
			}

Daniel Martí's avatar
Daniel Martí committed
216 217 218
			v, n := protowire.ConsumeVarint(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
219
			}
Daniel Martí's avatar
Daniel Martí committed
220 221
			remaining = remaining[n:]

Rod Vagg's avatar
Rod Vagg committed
222
			if err := ma.AssembleKey().AssignString("Tsize"); err != nil {
Rod Vagg's avatar
Rod Vagg committed
223 224
				return err
			}
225
			if err := ma.AssembleValue().AssignInt(int64(v)); err != nil {
Rod Vagg's avatar
Rod Vagg committed
226 227 228
				return err
			}
			haveTsize = true
Daniel Martí's avatar
Daniel Martí committed
229 230

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

Rod Vagg's avatar
Rod Vagg committed
235
	if !haveHash {
Daniel Martí's avatar
Daniel Martí committed
236
		return fmt.Errorf("invalid Hash field found in link, expected CID")
Rod Vagg's avatar
Rod Vagg committed
237
	}
Rod Vagg's avatar
Rod Vagg committed
238 239 240

	return nil
}