unmarshal.go 5.42 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

Rod Vagg's avatar
Rod Vagg committed
18 19
// Unmarshal provides an IPLD codec decode interface for DAG-PB data. Provide
// a compatible NodeAssembler and a byte source to unmarshal a DAG-PB IPLD
Rod Vagg's avatar
Rod Vagg committed
20 21
// Node. Use the NodeAssembler from the PBNode type for safest construction
// (Type.PBNode.NewBuilder()). A Map assembler will also work.
Rod Vagg's avatar
Rod Vagg committed
22
func Unmarshal(na ipld.NodeAssembler, in io.Reader) error {
Daniel Martí's avatar
Daniel Martí committed
23 24 25 26 27 28 29 30 31 32 33
	var remaining []byte
	if buf, ok := in.(interface{ Bytes() []byte }); ok {
		remaining = buf.Bytes()
	} else {
		var err error
		remaining, err = ioutil.ReadAll(in)
		if err != nil {
			return err
		}
	}

Rod Vagg's avatar
Rod Vagg committed
34 35 36 37
	ma, err := na.BeginMap(2)
	if err != nil {
		return err
	}
Rod Vagg's avatar
Rod Vagg committed
38
	// always make "Links", even if we don't use it
Daniel Martí's avatar
Daniel Martí committed
39
	if err := ma.AssembleKey().AssignString("Links"); err != nil {
Rod Vagg's avatar
Rod Vagg committed
40 41 42 43 44 45 46
		return err
	}
	links, err := ma.AssembleValue().BeginList(0)
	if err != nil {
		return err
	}

Rod Vagg's avatar
Rod Vagg committed
47 48
	haveData := false
	for {
Daniel Martí's avatar
Daniel Martí committed
49
		if len(remaining) == 0 {
Rod Vagg's avatar
Rod Vagg committed
50 51 52
			break
		}

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

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

Daniel Martí's avatar
Daniel Martí committed
63 64
		switch fieldNum {
		case 1:
Rod Vagg's avatar
Rod Vagg committed
65
			if haveData {
Daniel Martí's avatar
Daniel Martí committed
66
				return fmt.Errorf("protobuf: (PBNode) duplicate Data section")
Rod Vagg's avatar
Rod Vagg committed
67
			}
Daniel Martí's avatar
Daniel Martí committed
68 69 70 71

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

Rod Vagg's avatar
Rod Vagg committed
75 76
			// 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
77 78 79 80 81 82 83
			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
84
			if err := ma.AssembleValue().AssignBytes(chunk); err != nil {
Rod Vagg's avatar
Rod Vagg committed
85 86
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
87
			haveData = true
Daniel Martí's avatar
Daniel Martí committed
88 89

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

Daniel Martí's avatar
Daniel Martí committed
94 95 96
			bytesLen, n := protowire.ConsumeVarint(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
97
			}
Daniel Martí's avatar
Daniel Martí committed
98 99
			remaining = remaining[n:]

Rod Vagg's avatar
Rod Vagg committed
100
			curLink, err := links.AssembleValue().BeginMap(3)
Rod Vagg's avatar
Rod Vagg committed
101 102 103
			if err != nil {
				return err
			}
Daniel Martí's avatar
Daniel Martí committed
104
			if err := unmarshalLink(remaining[:bytesLen], curLink); err != nil {
Rod Vagg's avatar
Rod Vagg committed
105 106
				return err
			}
Daniel Martí's avatar
Daniel Martí committed
107
			remaining = remaining[bytesLen:]
Rod Vagg's avatar
Rod Vagg committed
108
			if err := curLink.Finish(); err != nil {
Rod Vagg's avatar
Rod Vagg committed
109 110
				return err
			}
Daniel Martí's avatar
Daniel Martí committed
111 112

		default:
Daniel Martí's avatar
Daniel Martí committed
113
			return fmt.Errorf("protobuf: (PBNode) invalid fieldNumber, expected 1 or 2, got %d", fieldNum)
Rod Vagg's avatar
Rod Vagg committed
114 115 116 117 118 119 120 121 122 123 124
		}
	}

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

Daniel Martí's avatar
Daniel Martí committed
125
func unmarshalLink(remaining []byte, ma ipld.MapAssembler) error {
Rod Vagg's avatar
Rod Vagg committed
126 127 128 129
	haveHash := false
	haveName := false
	haveTsize := false
	for {
Daniel Martí's avatar
Daniel Martí committed
130
		if len(remaining) == 0 {
Rod Vagg's avatar
Rod Vagg committed
131 132
			break
		}
Daniel Martí's avatar
Daniel Martí committed
133 134 135 136

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

Daniel Martí's avatar
Daniel Martí committed
140 141
		switch fieldNum {
		case 1:
Rod Vagg's avatar
Rod Vagg committed
142
			if haveHash {
Daniel Martí's avatar
Daniel Martí committed
143
				return fmt.Errorf("protobuf: (PBLink) duplicate Hash section")
Rod Vagg's avatar
Rod Vagg committed
144 145
			}
			if haveName {
Daniel Martí's avatar
Daniel Martí committed
146
				return fmt.Errorf("protobuf: (PBLink) invalid order, found Name before Hash")
Rod Vagg's avatar
Rod Vagg committed
147 148
			}
			if haveTsize {
Daniel Martí's avatar
Daniel Martí committed
149
				return fmt.Errorf("protobuf: (PBLink) invalid order, found Tsize before Hash")
Rod Vagg's avatar
Rod Vagg committed
150 151
			}
			if wireType != 2 {
Daniel Martí's avatar
Daniel Martí committed
152
				return fmt.Errorf("protobuf: (PBLink) wrong wireType (%d) for Hash", wireType)
Rod Vagg's avatar
Rod Vagg committed
153 154
			}

Daniel Martí's avatar
Daniel Martí committed
155 156 157
			chunk, n := protowire.ConsumeBytes(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
158
			}
Daniel Martí's avatar
Daniel Martí committed
159 160 161 162
			remaining = remaining[n:]

			_, c, err := cid.CidFromBytes(chunk)
			if err != nil {
Daniel Martí's avatar
Daniel Martí committed
163
				return fmt.Errorf("invalid Hash field found in link, expected CID (%v)", err)
Rod Vagg's avatar
Rod Vagg committed
164 165
			}
			if err := ma.AssembleKey().AssignString("Hash"); err != nil {
Rod Vagg's avatar
Rod Vagg committed
166 167
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
168
			if err := ma.AssembleValue().AssignLink(cidlink.Link{Cid: c}); err != nil {
Rod Vagg's avatar
Rod Vagg committed
169 170
				return err
			}
Rod Vagg's avatar
Rod Vagg committed
171
			haveHash = true
Daniel Martí's avatar
Daniel Martí committed
172 173

		case 2:
Rod Vagg's avatar
Rod Vagg committed
174
			if haveName {
Daniel Martí's avatar
Daniel Martí committed
175
				return fmt.Errorf("protobuf: (PBLink) duplicate Name section")
Rod Vagg's avatar
Rod Vagg committed
176 177
			}
			if haveTsize {
Daniel Martí's avatar
Daniel Martí committed
178
				return fmt.Errorf("protobuf: (PBLink) invalid order, found Tsize before Name")
Rod Vagg's avatar
Rod Vagg committed
179 180
			}
			if wireType != 2 {
Daniel Martí's avatar
Daniel Martí committed
181
				return fmt.Errorf("protobuf: (PBLink) wrong wireType (%d) for Name", wireType)
Rod Vagg's avatar
Rod Vagg committed
182 183
			}

Daniel Martí's avatar
Daniel Martí committed
184 185 186
			chunk, n := protowire.ConsumeBytes(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
187
			}
Daniel Martí's avatar
Daniel Martí committed
188 189
			remaining = remaining[n:]

Rod Vagg's avatar
Rod Vagg committed
190 191 192 193 194 195 196
			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
197 198

		case 3:
Rod Vagg's avatar
Rod Vagg committed
199
			if haveTsize {
Daniel Martí's avatar
Daniel Martí committed
200
				return fmt.Errorf("protobuf: (PBLink) duplicate Tsize section")
Rod Vagg's avatar
Rod Vagg committed
201 202
			}
			if wireType != 0 {
Daniel Martí's avatar
Daniel Martí committed
203
				return fmt.Errorf("protobuf: (PBLink) wrong wireType (%d) for Tsize", wireType)
Rod Vagg's avatar
Rod Vagg committed
204 205
			}

Daniel Martí's avatar
Daniel Martí committed
206 207 208
			v, n := protowire.ConsumeVarint(remaining)
			if n < 0 {
				return protowire.ParseError(n)
Rod Vagg's avatar
Rod Vagg committed
209
			}
Daniel Martí's avatar
Daniel Martí committed
210 211
			remaining = remaining[n:]

Rod Vagg's avatar
Rod Vagg committed
212
			if err := ma.AssembleKey().AssignString("Tsize"); err != nil {
Rod Vagg's avatar
Rod Vagg committed
213 214
				return err
			}
215
			if err := ma.AssembleValue().AssignInt(int64(v)); err != nil {
Rod Vagg's avatar
Rod Vagg committed
216 217 218
				return err
			}
			haveTsize = true
Daniel Martí's avatar
Daniel Martí committed
219 220

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

Rod Vagg's avatar
Rod Vagg committed
225
	if !haveHash {
Daniel Martí's avatar
Daniel Martí committed
226
		return fmt.Errorf("invalid Hash field found in link, expected CID")
Rod Vagg's avatar
Rod Vagg committed
227
	}
Rod Vagg's avatar
Rod Vagg committed
228 229 230

	return nil
}