1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package protowire
6
7import (
8	"bytes"
9	"encoding/hex"
10	"io"
11	"math"
12	"strings"
13	"testing"
14)
15
16type (
17	testOps struct {
18		// appendOps is a sequence of append operations, each appending to
19		// the output of the previous append operation.
20		appendOps []appendOp
21
22		// wantRaw (if not nil) is the bytes that the appendOps should produce.
23		wantRaw []byte
24
25		// consumeOps are a sequence of consume operations, each consuming the
26		// remaining output after the previous consume operation.
27		// The first consume operation starts with the output of appendOps.
28		consumeOps []consumeOp
29	}
30
31	// appendOp represents an Append operation.
32	appendOp  = interface{}
33	appendTag struct {
34		inNum  Number
35		inType Type
36	}
37	appendVarint struct {
38		inVal uint64
39	}
40	appendFixed32 struct {
41		inVal uint32
42	}
43	appendFixed64 struct {
44		inVal uint64
45	}
46	appendBytes struct {
47		inVal []byte
48	}
49	appendGroup struct {
50		inNum Number
51		inVal []byte
52	}
53	appendRaw []byte
54
55	// consumeOp represents an Consume operation.
56	consumeOp    = interface{}
57	consumeField struct {
58		wantNum  Number
59		wantType Type
60		wantCnt  int
61		wantErr  error
62	}
63	consumeFieldValue struct {
64		inNum   Number
65		inType  Type
66		wantCnt int
67		wantErr error
68	}
69	consumeTag struct {
70		wantNum  Number
71		wantType Type
72		wantCnt  int
73		wantErr  error
74	}
75	consumeVarint struct {
76		wantVal uint64
77		wantCnt int
78		wantErr error
79	}
80	consumeFixed32 struct {
81		wantVal uint32
82		wantCnt int
83		wantErr error
84	}
85	consumeFixed64 struct {
86		wantVal uint64
87		wantCnt int
88		wantErr error
89	}
90	consumeBytes struct {
91		wantVal []byte
92		wantCnt int
93		wantErr error
94	}
95	consumeGroup struct {
96		inNum   Number
97		wantVal []byte
98		wantCnt int
99		wantErr error
100	}
101
102	ops []interface{}
103)
104
105// dhex decodes a hex-string and returns the bytes and panics if s is invalid.
106func dhex(s string) []byte {
107	b, err := hex.DecodeString(s)
108	if err != nil {
109		panic(err)
110	}
111	return b
112}
113
114func TestTag(t *testing.T) {
115	runTests(t, []testOps{{
116		appendOps:  ops{appendRaw(dhex(""))},
117		consumeOps: ops{consumeTag{wantErr: io.ErrUnexpectedEOF}},
118	}, {
119		appendOps:  ops{appendTag{inNum: 0, inType: Fixed32Type}},
120		wantRaw:    dhex("05"),
121		consumeOps: ops{consumeTag{wantErr: errFieldNumber}},
122	}, {
123		appendOps:  ops{appendTag{inNum: 1, inType: Fixed32Type}},
124		wantRaw:    dhex("0d"),
125		consumeOps: ops{consumeTag{wantNum: 1, wantType: Fixed32Type, wantCnt: 1}},
126	}, {
127		appendOps:  ops{appendTag{inNum: FirstReservedNumber, inType: BytesType}},
128		wantRaw:    dhex("c2a309"),
129		consumeOps: ops{consumeTag{wantNum: FirstReservedNumber, wantType: BytesType, wantCnt: 3}},
130	}, {
131		appendOps:  ops{appendTag{inNum: LastReservedNumber, inType: StartGroupType}},
132		wantRaw:    dhex("fbe109"),
133		consumeOps: ops{consumeTag{wantNum: LastReservedNumber, wantType: StartGroupType, wantCnt: 3}},
134	}, {
135		appendOps:  ops{appendTag{inNum: MaxValidNumber, inType: VarintType}},
136		wantRaw:    dhex("f8ffffff0f"),
137		consumeOps: ops{consumeTag{wantNum: MaxValidNumber, wantType: VarintType, wantCnt: 5}},
138	}, {
139		appendOps:  ops{appendVarint{inVal: ((math.MaxInt32+1)<<3 | uint64(VarintType))}},
140		wantRaw:    dhex("8080808040"),
141		consumeOps: ops{consumeTag{wantErr: errFieldNumber}},
142	}})
143}
144
145func TestVarint(t *testing.T) {
146	runTests(t, []testOps{{
147		appendOps:  ops{appendRaw(dhex(""))},
148		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
149	}, {
150		appendOps:  ops{appendRaw(dhex("80"))},
151		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
152	}, {
153		appendOps:  ops{appendRaw(dhex("8080"))},
154		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
155	}, {
156		appendOps:  ops{appendRaw(dhex("808080"))},
157		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
158	}, {
159		appendOps:  ops{appendRaw(dhex("80808080"))},
160		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
161	}, {
162		appendOps:  ops{appendRaw(dhex("8080808080"))},
163		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
164	}, {
165		appendOps:  ops{appendRaw(dhex("808080808080"))},
166		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
167	}, {
168		appendOps:  ops{appendRaw(dhex("80808080808080"))},
169		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
170	}, {
171		appendOps:  ops{appendRaw(dhex("8080808080808080"))},
172		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
173	}, {
174		appendOps:  ops{appendRaw(dhex("808080808080808080"))},
175		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
176	}, {
177		appendOps:  ops{appendRaw(dhex("80808080808080808080"))},
178		consumeOps: ops{consumeVarint{wantErr: errOverflow}},
179	}, {
180		// Test varints at various boundaries where the length changes.
181		appendOps:  ops{appendVarint{inVal: 0x0}},
182		wantRaw:    dhex("00"),
183		consumeOps: ops{consumeVarint{wantVal: 0, wantCnt: 1}},
184	}, {
185		appendOps:  ops{appendVarint{inVal: 0x1}},
186		wantRaw:    dhex("01"),
187		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 1}},
188	}, {
189		appendOps:  ops{appendVarint{inVal: 0x7f}},
190		wantRaw:    dhex("7f"),
191		consumeOps: ops{consumeVarint{wantVal: 0x7f, wantCnt: 1}},
192	}, {
193		appendOps:  ops{appendVarint{inVal: 0x7f + 1}},
194		wantRaw:    dhex("8001"),
195		consumeOps: ops{consumeVarint{wantVal: 0x7f + 1, wantCnt: 2}},
196	}, {
197		appendOps:  ops{appendVarint{inVal: 0x3fff}},
198		wantRaw:    dhex("ff7f"),
199		consumeOps: ops{consumeVarint{wantVal: 0x3fff, wantCnt: 2}},
200	}, {
201		appendOps:  ops{appendVarint{inVal: 0x3fff + 1}},
202		wantRaw:    dhex("808001"),
203		consumeOps: ops{consumeVarint{wantVal: 0x3fff + 1, wantCnt: 3}},
204	}, {
205		appendOps:  ops{appendVarint{inVal: 0x1fffff}},
206		wantRaw:    dhex("ffff7f"),
207		consumeOps: ops{consumeVarint{wantVal: 0x1fffff, wantCnt: 3}},
208	}, {
209		appendOps:  ops{appendVarint{inVal: 0x1fffff + 1}},
210		wantRaw:    dhex("80808001"),
211		consumeOps: ops{consumeVarint{wantVal: 0x1fffff + 1, wantCnt: 4}},
212	}, {
213		appendOps:  ops{appendVarint{inVal: 0xfffffff}},
214		wantRaw:    dhex("ffffff7f"),
215		consumeOps: ops{consumeVarint{wantVal: 0xfffffff, wantCnt: 4}},
216	}, {
217		appendOps:  ops{appendVarint{inVal: 0xfffffff + 1}},
218		wantRaw:    dhex("8080808001"),
219		consumeOps: ops{consumeVarint{wantVal: 0xfffffff + 1, wantCnt: 5}},
220	}, {
221		appendOps:  ops{appendVarint{inVal: 0x7ffffffff}},
222		wantRaw:    dhex("ffffffff7f"),
223		consumeOps: ops{consumeVarint{wantVal: 0x7ffffffff, wantCnt: 5}},
224	}, {
225		appendOps:  ops{appendVarint{inVal: 0x7ffffffff + 1}},
226		wantRaw:    dhex("808080808001"),
227		consumeOps: ops{consumeVarint{wantVal: 0x7ffffffff + 1, wantCnt: 6}},
228	}, {
229		appendOps:  ops{appendVarint{inVal: 0x3ffffffffff}},
230		wantRaw:    dhex("ffffffffff7f"),
231		consumeOps: ops{consumeVarint{wantVal: 0x3ffffffffff, wantCnt: 6}},
232	}, {
233		appendOps:  ops{appendVarint{inVal: 0x3ffffffffff + 1}},
234		wantRaw:    dhex("80808080808001"),
235		consumeOps: ops{consumeVarint{wantVal: 0x3ffffffffff + 1, wantCnt: 7}},
236	}, {
237		appendOps:  ops{appendVarint{inVal: 0x1ffffffffffff}},
238		wantRaw:    dhex("ffffffffffff7f"),
239		consumeOps: ops{consumeVarint{wantVal: 0x1ffffffffffff, wantCnt: 7}},
240	}, {
241		appendOps:  ops{appendVarint{inVal: 0x1ffffffffffff + 1}},
242		wantRaw:    dhex("8080808080808001"),
243		consumeOps: ops{consumeVarint{wantVal: 0x1ffffffffffff + 1, wantCnt: 8}},
244	}, {
245		appendOps:  ops{appendVarint{inVal: 0xffffffffffffff}},
246		wantRaw:    dhex("ffffffffffffff7f"),
247		consumeOps: ops{consumeVarint{wantVal: 0xffffffffffffff, wantCnt: 8}},
248	}, {
249		appendOps:  ops{appendVarint{inVal: 0xffffffffffffff + 1}},
250		wantRaw:    dhex("808080808080808001"),
251		consumeOps: ops{consumeVarint{wantVal: 0xffffffffffffff + 1, wantCnt: 9}},
252	}, {
253		appendOps:  ops{appendVarint{inVal: 0x7fffffffffffffff}},
254		wantRaw:    dhex("ffffffffffffffff7f"),
255		consumeOps: ops{consumeVarint{wantVal: 0x7fffffffffffffff, wantCnt: 9}},
256	}, {
257		appendOps:  ops{appendVarint{inVal: 0x7fffffffffffffff + 1}},
258		wantRaw:    dhex("80808080808080808001"),
259		consumeOps: ops{consumeVarint{wantVal: 0x7fffffffffffffff + 1, wantCnt: 10}},
260	}, {
261		appendOps:  ops{appendVarint{inVal: math.MaxUint64}},
262		wantRaw:    dhex("ffffffffffffffffff01"),
263		consumeOps: ops{consumeVarint{wantVal: math.MaxUint64, wantCnt: 10}},
264	}, {
265		appendOps:  ops{appendRaw(dhex("ffffffffffffffffff02"))},
266		consumeOps: ops{consumeVarint{wantErr: errOverflow}},
267	}, {
268		// Test denormalized varints; where the encoding, while valid, is
269		// larger than necessary.
270		appendOps:  ops{appendRaw(dhex("01"))},
271		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 1}},
272	}, {
273		appendOps:  ops{appendRaw(dhex("8100"))},
274		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 2}},
275	}, {
276		appendOps:  ops{appendRaw(dhex("818000"))},
277		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 3}},
278	}, {
279		appendOps:  ops{appendRaw(dhex("81808000"))},
280		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 4}},
281	}, {
282		appendOps:  ops{appendRaw(dhex("8180808000"))},
283		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 5}},
284	}, {
285		appendOps:  ops{appendRaw(dhex("818080808000"))},
286		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 6}},
287	}, {
288		appendOps:  ops{appendRaw(dhex("81808080808000"))},
289		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 7}},
290	}, {
291		appendOps:  ops{appendRaw(dhex("8180808080808000"))},
292		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 8}},
293	}, {
294		appendOps:  ops{appendRaw(dhex("818080808080808000"))},
295		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 9}},
296	}, {
297		appendOps:  ops{appendRaw(dhex("81808080808080808000"))},
298		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 10}},
299	}, {
300		appendOps:  ops{appendRaw(dhex("8180808080808080808000"))},
301		consumeOps: ops{consumeVarint{wantErr: errOverflow}},
302	}})
303}
304
305func TestFixed32(t *testing.T) {
306	runTests(t, []testOps{{
307		appendOps:  ops{appendRaw(dhex(""))},
308		consumeOps: ops{consumeFixed32{wantErr: io.ErrUnexpectedEOF}},
309	}, {
310		appendOps:  ops{appendRaw(dhex("000000"))},
311		consumeOps: ops{consumeFixed32{wantErr: io.ErrUnexpectedEOF}},
312	}, {
313		appendOps:  ops{appendFixed32{0}},
314		wantRaw:    dhex("00000000"),
315		consumeOps: ops{consumeFixed32{wantVal: 0, wantCnt: 4}},
316	}, {
317		appendOps:  ops{appendFixed32{math.MaxUint32}},
318		wantRaw:    dhex("ffffffff"),
319		consumeOps: ops{consumeFixed32{wantVal: math.MaxUint32, wantCnt: 4}},
320	}, {
321		appendOps:  ops{appendFixed32{0xf0e1d2c3}},
322		wantRaw:    dhex("c3d2e1f0"),
323		consumeOps: ops{consumeFixed32{wantVal: 0xf0e1d2c3, wantCnt: 4}},
324	}})
325}
326
327func TestFixed64(t *testing.T) {
328	runTests(t, []testOps{{
329		appendOps:  ops{appendRaw(dhex(""))},
330		consumeOps: ops{consumeFixed64{wantErr: io.ErrUnexpectedEOF}},
331	}, {
332		appendOps:  ops{appendRaw(dhex("00000000000000"))},
333		consumeOps: ops{consumeFixed64{wantErr: io.ErrUnexpectedEOF}},
334	}, {
335		appendOps:  ops{appendFixed64{0}},
336		wantRaw:    dhex("0000000000000000"),
337		consumeOps: ops{consumeFixed64{wantVal: 0, wantCnt: 8}},
338	}, {
339		appendOps:  ops{appendFixed64{math.MaxUint64}},
340		wantRaw:    dhex("ffffffffffffffff"),
341		consumeOps: ops{consumeFixed64{wantVal: math.MaxUint64, wantCnt: 8}},
342	}, {
343		appendOps:  ops{appendFixed64{0xf0e1d2c3b4a59687}},
344		wantRaw:    dhex("8796a5b4c3d2e1f0"),
345		consumeOps: ops{consumeFixed64{wantVal: 0xf0e1d2c3b4a59687, wantCnt: 8}},
346	}})
347}
348
349func TestBytes(t *testing.T) {
350	runTests(t, []testOps{{
351		appendOps:  ops{appendRaw(dhex(""))},
352		consumeOps: ops{consumeBytes{wantErr: io.ErrUnexpectedEOF}},
353	}, {
354		appendOps:  ops{appendRaw(dhex("01"))},
355		consumeOps: ops{consumeBytes{wantErr: io.ErrUnexpectedEOF}},
356	}, {
357		appendOps:  ops{appendVarint{0}, appendRaw("")},
358		wantRaw:    dhex("00"),
359		consumeOps: ops{consumeBytes{wantVal: dhex(""), wantCnt: 1}},
360	}, {
361		appendOps:  ops{appendBytes{[]byte("hello")}},
362		wantRaw:    []byte("\x05hello"),
363		consumeOps: ops{consumeBytes{wantVal: []byte("hello"), wantCnt: 6}},
364	}, {
365		appendOps:  ops{appendBytes{[]byte(strings.Repeat("hello", 50))}},
366		wantRaw:    []byte("\xfa\x01" + strings.Repeat("hello", 50)),
367		consumeOps: ops{consumeBytes{wantVal: []byte(strings.Repeat("hello", 50)), wantCnt: 252}},
368	}, {
369		appendOps:  ops{appendRaw("\x85\x80\x00hello")},
370		consumeOps: ops{consumeBytes{wantVal: []byte("hello"), wantCnt: 8}},
371	}, {
372		appendOps:  ops{appendRaw("\x85\x80\x00hell")},
373		consumeOps: ops{consumeBytes{wantErr: io.ErrUnexpectedEOF}},
374	}})
375}
376
377func TestGroup(t *testing.T) {
378	runTests(t, []testOps{{
379		appendOps:  ops{appendRaw(dhex(""))},
380		consumeOps: ops{consumeGroup{wantErr: io.ErrUnexpectedEOF}},
381	}, {
382		appendOps:  ops{appendTag{inNum: 0, inType: StartGroupType}},
383		consumeOps: ops{consumeGroup{inNum: 1, wantErr: errFieldNumber}},
384	}, {
385		appendOps:  ops{appendTag{inNum: 2, inType: EndGroupType}},
386		consumeOps: ops{consumeGroup{inNum: 1, wantErr: errEndGroup}},
387	}, {
388		appendOps:  ops{appendTag{inNum: 1, inType: EndGroupType}},
389		consumeOps: ops{consumeGroup{inNum: 1, wantCnt: 1}},
390	}, {
391		appendOps: ops{
392			appendTag{inNum: 5, inType: Fixed32Type},
393			appendFixed32{0xf0e1d2c3},
394			appendTag{inNum: 5, inType: EndGroupType},
395		},
396		wantRaw:    dhex("2dc3d2e1f02c"),
397		consumeOps: ops{consumeGroup{inNum: 5, wantVal: dhex("2dc3d2e1f0"), wantCnt: 6}},
398	}, {
399		appendOps: ops{
400			appendTag{inNum: 5, inType: Fixed32Type},
401			appendFixed32{0xf0e1d2c3},
402			appendRaw(dhex("ac808000")),
403		},
404		consumeOps: ops{consumeGroup{inNum: 5, wantVal: dhex("2dc3d2e1f0"), wantCnt: 9}},
405	}})
406}
407
408func TestField(t *testing.T) {
409	runTests(t, []testOps{{
410		appendOps:  ops{appendRaw(dhex(""))},
411		consumeOps: ops{consumeField{wantErr: io.ErrUnexpectedEOF}},
412	}, {
413		appendOps: ops{
414			appendTag{inNum: 5000, inType: StartGroupType},
415			appendTag{inNum: 1, inType: VarintType},
416			appendVarint{123456789},
417			appendTag{inNum: 12, inType: Fixed32Type},
418			appendFixed32{123456789},
419			appendTag{inNum: 123, inType: Fixed64Type},
420			appendFixed64{123456789},
421			appendTag{inNum: 1234, inType: BytesType},
422			appendBytes{[]byte("hello")},
423			appendTag{inNum: 12345, inType: StartGroupType},
424			appendTag{inNum: 11, inType: VarintType},
425			appendVarint{123456789},
426			appendTag{inNum: 1212, inType: Fixed32Type},
427			appendFixed32{123456789},
428			appendTag{inNum: 123123, inType: Fixed64Type},
429			appendFixed64{123456789},
430			appendTag{inNum: 12341234, inType: BytesType},
431			appendBytes{[]byte("goodbye")},
432			appendTag{inNum: 12345, inType: EndGroupType},
433			appendTag{inNum: 5000, inType: EndGroupType},
434		},
435		wantRaw: dhex("c3b80208959aef3a6515cd5b07d90715cd5b0700000000924d0568656c6c6fcb830658959aef3ae54b15cd5b07998f3c15cd5b070000000092ff892f07676f6f64627965cc8306c4b802"),
436		consumeOps: ops{
437			consumeTag{wantNum: 5000, wantType: StartGroupType, wantCnt: 3},
438			consumeTag{wantNum: 1, wantType: VarintType, wantCnt: 1},
439			consumeVarint{wantVal: 123456789, wantCnt: 4},
440			consumeTag{wantNum: 12, wantType: Fixed32Type, wantCnt: 1},
441			consumeFixed32{wantVal: 123456789, wantCnt: 4},
442			consumeTag{wantNum: 123, wantType: Fixed64Type, wantCnt: 2},
443			consumeFixed64{wantVal: 123456789, wantCnt: 8},
444			consumeTag{wantNum: 1234, wantType: BytesType, wantCnt: 2},
445			consumeBytes{wantVal: []byte("hello"), wantCnt: 6},
446			consumeTag{wantNum: 12345, wantType: StartGroupType, wantCnt: 3},
447			consumeTag{wantNum: 11, wantType: VarintType, wantCnt: 1},
448			consumeVarint{wantVal: 123456789, wantCnt: 4},
449			consumeTag{wantNum: 1212, wantType: Fixed32Type, wantCnt: 2},
450			consumeFixed32{wantVal: 123456789, wantCnt: 4},
451			consumeTag{wantNum: 123123, wantType: Fixed64Type, wantCnt: 3},
452			consumeFixed64{wantVal: 123456789, wantCnt: 8},
453			consumeTag{wantNum: 12341234, wantType: BytesType, wantCnt: 4},
454			consumeBytes{wantVal: []byte("goodbye"), wantCnt: 8},
455			consumeTag{wantNum: 12345, wantType: EndGroupType, wantCnt: 3},
456			consumeTag{wantNum: 5000, wantType: EndGroupType, wantCnt: 3},
457		},
458	}, {
459		appendOps:  ops{appendRaw(dhex("c3b80208959aef3a6515cd5b07d90715cd5b0700000000924d0568656c6c6fcb830658959aef3ae54b15cd5b07998f3c15cd5b070000000092ff892f07676f6f64627965cc8306c4b802"))},
460		consumeOps: ops{consumeField{wantNum: 5000, wantType: StartGroupType, wantCnt: 74}},
461	}, {
462		appendOps:  ops{appendTag{inNum: 5, inType: EndGroupType}},
463		wantRaw:    dhex("2c"),
464		consumeOps: ops{consumeField{wantErr: errEndGroup}},
465	}, {
466		appendOps: ops{
467			appendTag{inNum: 1, inType: StartGroupType},
468			appendTag{inNum: 22, inType: StartGroupType},
469			appendTag{inNum: 333, inType: StartGroupType},
470			appendTag{inNum: 4444, inType: StartGroupType},
471			appendTag{inNum: 4444, inType: EndGroupType},
472			appendTag{inNum: 333, inType: EndGroupType},
473			appendTag{inNum: 22, inType: EndGroupType},
474			appendTag{inNum: 1, inType: EndGroupType},
475		},
476		wantRaw:    dhex("0bb301eb14e39502e49502ec14b4010c"),
477		consumeOps: ops{consumeField{wantNum: 1, wantType: StartGroupType, wantCnt: 16}},
478	}, {
479		appendOps: ops{
480			appendTag{inNum: 1, inType: StartGroupType},
481			appendGroup{inNum: 1, inVal: dhex("b301eb14e39502e49502ec14b401")},
482		},
483		wantRaw:    dhex("0b" + "b301eb14e39502e49502ec14b401" + "0c"),
484		consumeOps: ops{consumeField{wantNum: 1, wantType: StartGroupType, wantCnt: 16}},
485	}, {
486		appendOps: ops{
487			appendTag{inNum: 1, inType: StartGroupType},
488			appendTag{inNum: 22, inType: StartGroupType},
489			appendTag{inNum: 333, inType: StartGroupType},
490			appendTag{inNum: 4444, inType: StartGroupType},
491			appendTag{inNum: 333, inType: EndGroupType},
492			appendTag{inNum: 22, inType: EndGroupType},
493			appendTag{inNum: 1, inType: EndGroupType},
494		},
495		consumeOps: ops{consumeField{wantErr: errEndGroup}},
496	}, {
497		appendOps: ops{
498			appendTag{inNum: 1, inType: StartGroupType},
499			appendTag{inNum: 22, inType: StartGroupType},
500			appendTag{inNum: 333, inType: StartGroupType},
501			appendTag{inNum: 4444, inType: StartGroupType},
502			appendTag{inNum: 4444, inType: EndGroupType},
503			appendTag{inNum: 333, inType: EndGroupType},
504			appendTag{inNum: 22, inType: EndGroupType},
505		},
506		consumeOps: ops{consumeField{wantErr: io.ErrUnexpectedEOF}},
507	}, {
508		appendOps: ops{
509			appendTag{inNum: 1, inType: StartGroupType},
510			appendTag{inNum: 22, inType: StartGroupType},
511			appendTag{inNum: 333, inType: StartGroupType},
512			appendTag{inNum: 4444, inType: StartGroupType},
513			appendTag{inNum: 0, inType: VarintType},
514			appendTag{inNum: 4444, inType: EndGroupType},
515			appendTag{inNum: 333, inType: EndGroupType},
516			appendTag{inNum: 22, inType: EndGroupType},
517			appendTag{inNum: 1, inType: EndGroupType},
518		},
519		consumeOps: ops{consumeField{wantErr: errFieldNumber}},
520	}, {
521		appendOps: ops{
522			appendTag{inNum: 1, inType: StartGroupType},
523			appendTag{inNum: 22, inType: StartGroupType},
524			appendTag{inNum: 333, inType: StartGroupType},
525			appendTag{inNum: 4444, inType: StartGroupType},
526			appendTag{inNum: 1, inType: 6},
527			appendTag{inNum: 4444, inType: EndGroupType},
528			appendTag{inNum: 333, inType: EndGroupType},
529			appendTag{inNum: 22, inType: EndGroupType},
530			appendTag{inNum: 1, inType: EndGroupType},
531		},
532		consumeOps: ops{consumeField{wantErr: errReserved}},
533	}})
534}
535
536func runTests(t *testing.T, tests []testOps) {
537	for _, tt := range tests {
538		t.Run("", func(t *testing.T) {
539			var b []byte
540			for _, op := range tt.appendOps {
541				b0 := b
542				switch op := op.(type) {
543				case appendTag:
544					b = AppendTag(b, op.inNum, op.inType)
545				case appendVarint:
546					b = AppendVarint(b, op.inVal)
547				case appendFixed32:
548					b = AppendFixed32(b, op.inVal)
549				case appendFixed64:
550					b = AppendFixed64(b, op.inVal)
551				case appendBytes:
552					b = AppendBytes(b, op.inVal)
553				case appendGroup:
554					b = AppendGroup(b, op.inNum, op.inVal)
555				case appendRaw:
556					b = append(b, op...)
557				}
558
559				check := func(label string, want int) {
560					t.Helper()
561					if got := len(b) - len(b0); got != want {
562						t.Errorf("len(Append%v) and Size%v mismatch: got %v, want %v", label, label, got, want)
563					}
564				}
565				switch op := op.(type) {
566				case appendTag:
567					check("Tag", SizeTag(op.inNum))
568				case appendVarint:
569					check("Varint", SizeVarint(op.inVal))
570				case appendFixed32:
571					check("Fixed32", SizeFixed32())
572				case appendFixed64:
573					check("Fixed64", SizeFixed64())
574				case appendBytes:
575					check("Bytes", SizeBytes(len(op.inVal)))
576				case appendGroup:
577					check("Group", SizeGroup(op.inNum, len(op.inVal)))
578				}
579			}
580
581			if tt.wantRaw != nil && !bytes.Equal(b, tt.wantRaw) {
582				t.Errorf("raw output mismatch:\ngot  %x\nwant %x", b, tt.wantRaw)
583			}
584
585			for _, op := range tt.consumeOps {
586				check := func(label string, gotCnt, wantCnt int, wantErr error) {
587					t.Helper()
588					gotErr := ParseError(gotCnt)
589					if gotCnt < 0 {
590						gotCnt = 0
591					}
592					if gotCnt != wantCnt {
593						t.Errorf("Consume%v(): consumed %d bytes, want %d bytes consumed", label, gotCnt, wantCnt)
594					}
595					if gotErr != wantErr {
596						t.Errorf("Consume%v(): got %v error, want %v error", label, gotErr, wantErr)
597					}
598					b = b[gotCnt:]
599				}
600				switch op := op.(type) {
601				case consumeField:
602					gotNum, gotType, n := ConsumeField(b)
603					if gotNum != op.wantNum || gotType != op.wantType {
604						t.Errorf("ConsumeField() = (%d, %v), want (%d, %v)", gotNum, gotType, op.wantNum, op.wantType)
605					}
606					check("Field", n, op.wantCnt, op.wantErr)
607				case consumeFieldValue:
608					n := ConsumeFieldValue(op.inNum, op.inType, b)
609					check("FieldValue", n, op.wantCnt, op.wantErr)
610				case consumeTag:
611					gotNum, gotType, n := ConsumeTag(b)
612					if gotNum != op.wantNum || gotType != op.wantType {
613						t.Errorf("ConsumeTag() = (%d, %v), want (%d, %v)", gotNum, gotType, op.wantNum, op.wantType)
614					}
615					check("Tag", n, op.wantCnt, op.wantErr)
616				case consumeVarint:
617					gotVal, n := ConsumeVarint(b)
618					if gotVal != op.wantVal {
619						t.Errorf("ConsumeVarint() = %d, want %d", gotVal, op.wantVal)
620					}
621					check("Varint", n, op.wantCnt, op.wantErr)
622				case consumeFixed32:
623					gotVal, n := ConsumeFixed32(b)
624					if gotVal != op.wantVal {
625						t.Errorf("ConsumeFixed32() = %d, want %d", gotVal, op.wantVal)
626					}
627					check("Fixed32", n, op.wantCnt, op.wantErr)
628				case consumeFixed64:
629					gotVal, n := ConsumeFixed64(b)
630					if gotVal != op.wantVal {
631						t.Errorf("ConsumeFixed64() = %d, want %d", gotVal, op.wantVal)
632					}
633					check("Fixed64", n, op.wantCnt, op.wantErr)
634				case consumeBytes:
635					gotVal, n := ConsumeBytes(b)
636					if !bytes.Equal(gotVal, op.wantVal) {
637						t.Errorf("ConsumeBytes() = %x, want %x", gotVal, op.wantVal)
638					}
639					check("Bytes", n, op.wantCnt, op.wantErr)
640				case consumeGroup:
641					gotVal, n := ConsumeGroup(op.inNum, b)
642					if !bytes.Equal(gotVal, op.wantVal) {
643						t.Errorf("ConsumeGroup() = %x, want %x", gotVal, op.wantVal)
644					}
645					check("Group", n, op.wantCnt, op.wantErr)
646				}
647			}
648		})
649	}
650}
651
652func TestZigZag(t *testing.T) {
653	tests := []struct {
654		dec int64
655		enc uint64
656	}{
657		{math.MinInt64 + 0, math.MaxUint64 - 0},
658		{math.MinInt64 + 1, math.MaxUint64 - 2},
659		{math.MinInt64 + 2, math.MaxUint64 - 4},
660		{-3, 5},
661		{-2, 3},
662		{-1, 1},
663		{0, 0},
664		{+1, 2},
665		{+2, 4},
666		{+3, 6},
667		{math.MaxInt64 - 2, math.MaxUint64 - 5},
668		{math.MaxInt64 - 1, math.MaxUint64 - 3},
669		{math.MaxInt64 - 0, math.MaxUint64 - 1},
670	}
671
672	for _, tt := range tests {
673		if enc := EncodeZigZag(tt.dec); enc != tt.enc {
674			t.Errorf("EncodeZigZag(%d) = %d, want %d", tt.dec, enc, tt.enc)
675		}
676		if dec := DecodeZigZag(tt.enc); dec != tt.dec {
677			t.Errorf("DecodeZigZag(%d) = %d, want %d", tt.enc, dec, tt.dec)
678		}
679	}
680}
681