internal/encoding/wire: initial commit

This adds package wire, which provides low-level functionality for
marshaling and unmarshaling the protobuf wire format.

High-level API:
	type Number int32
	    const MinValidNumber Number = 1 ...
	type Type int8
	    const VarintType Type = 0 ...
	func ParseError(n int) error

	func ConsumeField(b []byte) (Number, Type, int)
	func ConsumeFieldValue(num Number, typ Type, b []byte) (n int)
	func ConsumeTag(b []byte) (Number, Type, int)
	func ConsumeVarint(b []byte) (v uint64, n int)
	func ConsumeFixed32(b []byte) (v uint32, n int)
	func ConsumeFixed64(b []byte) (v uint64, n int)
	func ConsumeBytes(b []byte) (v []byte, n int)
	func ConsumeGroup(num Number, b []byte) (v []byte, n int)
	func AppendTag(b []byte, num Number, typ Type) []byte
	func AppendVarint(b []byte, v uint64) []byte
	func AppendFixed32(b []byte, v uint32) []byte
	func AppendFixed64(b []byte, v uint64) []byte
	func AppendBytes(b []byte, v []byte) []byte
	func AppendGroup(b []byte, num Number, v []byte) []byte
	func SizeTag(num Number) int
	func SizeVarint(v uint64) int
	func SizeFixed32() int
	func SizeFixed64() int
	func SizeBytes(n int) int
	func SizeGroup(num Number, n int) int

	func DecodeBool(x uint64) bool
	func DecodeTag(x uint64) (Number, Type)
	func DecodeZigZag(x uint64) int64
	func EncodeBool(x bool) uint64
	func EncodeTag(num Number, typ Type) uint64
	func EncodeZigZag(x int64) uint64

Change-Id: I052d8975414aeb182f6e9595c4736e716f1b7e9d
Reviewed-on: https://go-review.googlesource.com/127337
Reviewed-by: Chris Manghane <cmang@golang.org>
Run-TryBot: Chris Manghane <cmang@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/encoding/wire/wire.go b/internal/encoding/wire/wire.go
new file mode 100644
index 0000000..ef4624c
--- /dev/null
+++ b/internal/encoding/wire/wire.go
@@ -0,0 +1,522 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package wire parses and formats the protobuf wire encoding.
+//
+// See https://developers.google.com/protocol-buffers/docs/encoding.
+package wire
+
+import (
+	"errors" // TODO: replace with our own errors package?
+	"io"
+	"math/bits"
+)
+
+// Number represents the field number.
+type Number int32
+
+const (
+	MinValidNumber      Number = 1
+	FirstReservedNumber Number = 19000
+	LastReservedNumber  Number = 19999
+	MaxValidNumber      Number = 1<<29 - 1
+)
+
+// IsValid reports whether the field number is semantically valid.
+//
+// Note that while numbers within the reserved range are semantically invalid,
+// they are syntactically valid in the wire format.
+// Implementations may treat records with reserved field numbers as unknown.
+func (n Number) IsValid() bool {
+	return MinValidNumber <= n && n < FirstReservedNumber || LastReservedNumber < n && n <= MaxValidNumber
+}
+
+// Type represents the wire type.
+type Type int8
+
+const (
+	VarintType     Type = 0
+	Fixed32Type    Type = 5
+	Fixed64Type    Type = 1
+	BytesType      Type = 2
+	StartGroupType Type = 3
+	EndGroupType   Type = 4
+)
+
+const (
+	_ = -iota
+	errCodeTruncated
+	errCodeFieldNumber
+	errCodeOverflow
+	errCodeReserved
+	errCodeEndGroup
+)
+
+var (
+	errFieldNumber = errors.New("invalid field number")
+	errOverflow    = errors.New("variable length integer overflow")
+	errReserved    = errors.New("cannot parse reserved wire type")
+	errEndGroup    = errors.New("mismatching end group marker")
+	errParse       = errors.New("parse error")
+)
+
+// ParseError converts an error code into an error value.
+// This returns nil if n is a non-negative number.
+func ParseError(n int) error {
+	if n >= 0 {
+		return nil
+	}
+	switch n {
+	case errCodeTruncated:
+		return io.ErrUnexpectedEOF
+	case errCodeFieldNumber:
+		return errFieldNumber
+	case errCodeOverflow:
+		return errOverflow
+	case errCodeReserved:
+		return errReserved
+	case errCodeEndGroup:
+		return errEndGroup
+	default:
+		return errParse
+	}
+}
+
+// ConsumeField parses an entire field record (both tag and value) and returns
+// the field number, the wire type, and the total length.
+// This returns a negative length upon an error (see ParseError).
+//
+// The total length includes the tag header and the end group marker (if the
+// field is a group).
+func ConsumeField(b []byte) (Number, Type, int) {
+	num, typ, n := ConsumeTag(b)
+	if n < 0 {
+		return 0, 0, n // forward error code
+	}
+	m := ConsumeFieldValue(num, typ, b[n:])
+	if m < 0 {
+		return 0, 0, m // forward error code
+	}
+	return num, typ, n + m
+}
+
+// ConsumeFieldValue parses a field value and returns its length.
+// This assumes that the field Number and wire Type have already been parsed.
+// This returns a negative length upon an error (see ParseError).
+//
+// When parsing a group, the length includes the end group marker and
+// the end group is verified to match the starting field number.
+func ConsumeFieldValue(num Number, typ Type, b []byte) (n int) {
+	switch typ {
+	case VarintType:
+		_, n = ConsumeVarint(b)
+		return n
+	case Fixed32Type:
+		_, n = ConsumeFixed32(b)
+		return n
+	case Fixed64Type:
+		_, n = ConsumeFixed64(b)
+		return n
+	case BytesType:
+		_, n = ConsumeBytes(b)
+		return n
+	case StartGroupType:
+		n0 := len(b)
+		for {
+			num2, typ2, n := ConsumeTag(b)
+			if n < 0 {
+				return n // forward error code
+			}
+			b = b[n:]
+			if typ2 == EndGroupType {
+				if num != num2 {
+					return errCodeEndGroup
+				}
+				return n0 - len(b)
+			}
+
+			n = ConsumeFieldValue(num2, typ2, b)
+			if n < 0 {
+				return n // forward error code
+			}
+			b = b[n:]
+		}
+	case EndGroupType:
+		return errCodeEndGroup
+	default:
+		return errCodeReserved
+	}
+}
+
+// AppendTag encodes num and typ as a varint-encoded tag and appends it to b.
+func AppendTag(b []byte, num Number, typ Type) []byte {
+	return AppendVarint(b, EncodeTag(num, typ))
+}
+
+// ConsumeTag parses b as a varint-encoded tag, reporting its length.
+// This returns a negative length upon an error (see ParseError).
+func ConsumeTag(b []byte) (Number, Type, int) {
+	v, n := ConsumeVarint(b)
+	if n < 0 {
+		return 0, 0, n // forward error code
+	}
+	num, typ := DecodeTag(v)
+	if num < MinValidNumber {
+		return 0, 0, errCodeFieldNumber
+	}
+	return num, typ, n
+}
+
+func SizeTag(num Number) int {
+	return SizeVarint(EncodeTag(num, 0)) // wire type has no effect on size
+}
+
+// AppendVarint appends v to b as a varint-encoded uint64.
+func AppendVarint(b []byte, v uint64) []byte {
+	// TODO: Specialize for sizes 1 and 2 with mid-stack inlining.
+	switch {
+	case v < 1<<7:
+		b = append(b, byte(v))
+	case v < 1<<14:
+		b = append(b,
+			byte((v>>0)&0x7f|0x80),
+			byte(v>>7))
+	case v < 1<<21:
+		b = append(b,
+			byte((v>>0)&0x7f|0x80),
+			byte((v>>7)&0x7f|0x80),
+			byte(v>>14))
+	case v < 1<<28:
+		b = append(b,
+			byte((v>>0)&0x7f|0x80),
+			byte((v>>7)&0x7f|0x80),
+			byte((v>>14)&0x7f|0x80),
+			byte(v>>21))
+	case v < 1<<35:
+		b = append(b,
+			byte((v>>0)&0x7f|0x80),
+			byte((v>>7)&0x7f|0x80),
+			byte((v>>14)&0x7f|0x80),
+			byte((v>>21)&0x7f|0x80),
+			byte(v>>28))
+	case v < 1<<42:
+		b = append(b,
+			byte((v>>0)&0x7f|0x80),
+			byte((v>>7)&0x7f|0x80),
+			byte((v>>14)&0x7f|0x80),
+			byte((v>>21)&0x7f|0x80),
+			byte((v>>28)&0x7f|0x80),
+			byte(v>>35))
+	case v < 1<<49:
+		b = append(b,
+			byte((v>>0)&0x7f|0x80),
+			byte((v>>7)&0x7f|0x80),
+			byte((v>>14)&0x7f|0x80),
+			byte((v>>21)&0x7f|0x80),
+			byte((v>>28)&0x7f|0x80),
+			byte((v>>35)&0x7f|0x80),
+			byte(v>>42))
+	case v < 1<<56:
+		b = append(b,
+			byte((v>>0)&0x7f|0x80),
+			byte((v>>7)&0x7f|0x80),
+			byte((v>>14)&0x7f|0x80),
+			byte((v>>21)&0x7f|0x80),
+			byte((v>>28)&0x7f|0x80),
+			byte((v>>35)&0x7f|0x80),
+			byte((v>>42)&0x7f|0x80),
+			byte(v>>49))
+	case v < 1<<63:
+		b = append(b,
+			byte((v>>0)&0x7f|0x80),
+			byte((v>>7)&0x7f|0x80),
+			byte((v>>14)&0x7f|0x80),
+			byte((v>>21)&0x7f|0x80),
+			byte((v>>28)&0x7f|0x80),
+			byte((v>>35)&0x7f|0x80),
+			byte((v>>42)&0x7f|0x80),
+			byte((v>>49)&0x7f|0x80),
+			byte(v>>56))
+	default:
+		b = append(b,
+			byte((v>>0)&0x7f|0x80),
+			byte((v>>7)&0x7f|0x80),
+			byte((v>>14)&0x7f|0x80),
+			byte((v>>21)&0x7f|0x80),
+			byte((v>>28)&0x7f|0x80),
+			byte((v>>35)&0x7f|0x80),
+			byte((v>>42)&0x7f|0x80),
+			byte((v>>49)&0x7f|0x80),
+			byte((v>>56)&0x7f|0x80),
+			1)
+	}
+	return b
+}
+
+// ConsumeVarint parses b as a varint-encoded uint64, reporting its length.
+// This returns a negative length upon an error (see ParseError).
+func ConsumeVarint(b []byte) (v uint64, n int) {
+	// TODO: Specialize for sizes 1 and 2 with mid-stack inlining.
+	var y uint64
+	if len(b) <= 0 {
+		return 0, errCodeTruncated
+	}
+	v = uint64(b[0])
+	if v < 0x80 {
+		return v, 1
+	}
+	v -= 0x80
+
+	if len(b) <= 1 {
+		return 0, errCodeTruncated
+	}
+	y = uint64(b[1])
+	v += y << 7
+	if y < 0x80 {
+		return v, 2
+	}
+	v -= 0x80 << 7
+
+	if len(b) <= 2 {
+		return 0, errCodeTruncated
+	}
+	y = uint64(b[2])
+	v += y << 14
+	if y < 0x80 {
+		return v, 3
+	}
+	v -= 0x80 << 14
+
+	if len(b) <= 3 {
+		return 0, errCodeTruncated
+	}
+	y = uint64(b[3])
+	v += y << 21
+	if y < 0x80 {
+		return v, 4
+	}
+	v -= 0x80 << 21
+
+	if len(b) <= 4 {
+		return 0, errCodeTruncated
+	}
+	y = uint64(b[4])
+	v += y << 28
+	if y < 0x80 {
+		return v, 5
+	}
+	v -= 0x80 << 28
+
+	if len(b) <= 5 {
+		return 0, errCodeTruncated
+	}
+	y = uint64(b[5])
+	v += y << 35
+	if y < 0x80 {
+		return v, 6
+	}
+	v -= 0x80 << 35
+
+	if len(b) <= 6 {
+		return 0, errCodeTruncated
+	}
+	y = uint64(b[6])
+	v += y << 42
+	if y < 0x80 {
+		return v, 7
+	}
+	v -= 0x80 << 42
+
+	if len(b) <= 7 {
+		return 0, errCodeTruncated
+	}
+	y = uint64(b[7])
+	v += y << 49
+	if y < 0x80 {
+		return v, 8
+	}
+	v -= 0x80 << 49
+
+	if len(b) <= 8 {
+		return 0, errCodeTruncated
+	}
+	y = uint64(b[8])
+	v += y << 56
+	if y < 0x80 {
+		return v, 9
+	}
+	v -= 0x80 << 56
+
+	if len(b) <= 9 {
+		return 0, errCodeTruncated
+	}
+	y = uint64(b[9])
+	v += y << 63
+	if y < 2 {
+		return v, 10
+	}
+	return 0, errCodeOverflow
+}
+
+// SizeVarint returns the encoded size of a varint.
+// The size is guaranteed to be within 1 and 10, inclusive.
+func SizeVarint(v uint64) int {
+	return 1 + (bits.Len64(v)-1)/7
+}
+
+// AppendFixed32 appends v to b as a little-endian uint32.
+func AppendFixed32(b []byte, v uint32) []byte {
+	return append(b,
+		byte(v>>0),
+		byte(v>>8),
+		byte(v>>16),
+		byte(v>>24))
+}
+
+// ConsumeFixed32 parses b as a little-endian uint32, reporting its length.
+// This returns a negative length upon an error (see ParseError).
+func ConsumeFixed32(b []byte) (v uint32, n int) {
+	if len(b) < 4 {
+		return 0, errCodeTruncated
+	}
+	v = uint32(b[0])<<0 | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
+	return v, 4
+}
+
+// SizeFixed32 returns the encoded size of a fixed32; which is always 4.
+func SizeFixed32() int {
+	return 4
+}
+
+// AppendFixed64 appends v to b as a little-endian uint64.
+func AppendFixed64(b []byte, v uint64) []byte {
+	return append(b,
+		byte(v>>0),
+		byte(v>>8),
+		byte(v>>16),
+		byte(v>>24),
+		byte(v>>32),
+		byte(v>>40),
+		byte(v>>48),
+		byte(v>>56))
+}
+
+// ConsumeFixed64 parses b as a little-endian uint64, reporting its length.
+// This returns a negative length upon an error (see ParseError).
+func ConsumeFixed64(b []byte) (v uint64, n int) {
+	if len(b) < 8 {
+		return 0, errCodeTruncated
+	}
+	v = uint64(b[0])<<0 | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
+	return v, 8
+}
+
+// SizeFixed64 returns the encoded size of a fixed64; which is always 8.
+func SizeFixed64() int {
+	return 8
+}
+
+// AppendBytes appends v to b as a length-prefixed bytes value.
+func AppendBytes(b []byte, v []byte) []byte {
+	return append(AppendVarint(b, uint64(len(v))), v...)
+}
+
+// ConsumeBytes parses b as a length-prefixed bytes value, reporting its length.
+// This returns a negative length upon an error (see ParseError).
+func ConsumeBytes(b []byte) (v []byte, n int) {
+	m, n := ConsumeVarint(b)
+	if n < 0 {
+		return nil, n // forward error code
+	}
+	if m > uint64(len(b[n:])) {
+		return nil, errCodeTruncated
+	}
+	return b[n:][:m], n + int(m)
+}
+
+// SizeBytes returns the encoded size of a length-prefixed bytes value,
+// given only the length.
+func SizeBytes(n int) int {
+	return SizeVarint(uint64(n)) + n
+}
+
+// AppendGroup appends v to b as group value, with a trailing end group marker.
+// The value v must not contain the end marker.
+func AppendGroup(b []byte, num Number, v []byte) []byte {
+	return AppendVarint(append(b, v...), EncodeTag(num, EndGroupType))
+}
+
+// ConsumeGroup parses b as a group value until the trailing end group marker,
+// and verifies that the end marker matches the provided num. The value v
+// does not contain the end marker, while the length does contain the end marker.
+// This returns a negative length upon an error (see ParseError).
+func ConsumeGroup(num Number, b []byte) (v []byte, n int) {
+	n = ConsumeFieldValue(num, StartGroupType, b)
+	if n < 0 {
+		return nil, n // forward error code
+	}
+	b = b[:n]
+
+	// Truncate off end group marker, but need to handle denormalized varints.
+	// Assuming end marker is never 0 (which is always the case since
+	// EndGroupType is non-zero), we can truncate all trailing bytes where the
+	// lower 7 bits are all zero (implying that the varint is denormalized).
+	for len(b) > 0 && b[len(b)-1]&0x7f == 0 {
+		b = b[:len(b)-1]
+	}
+	b = b[:len(b)-SizeTag(num)]
+	return b, n
+}
+
+// SizeGroup returns the encoded size of a group, given only the length.
+func SizeGroup(num Number, n int) int {
+	return n + SizeTag(num)
+}
+
+// DecodeTag decodes the field Number and wire Type from its unified form.
+// The Number is -1 if the decoded field number overflows.
+// Other than overflow, this does not check for field number validity.
+func DecodeTag(x uint64) (Number, Type) {
+	num := Number(x >> 3)
+	if num > MaxValidNumber {
+		num = -1
+	}
+	return num, Type(x & 7)
+}
+
+// EncodeTag encodes the field Number and wire Type into its unified form.
+func EncodeTag(num Number, typ Type) uint64 {
+	return uint64(num)<<3 | uint64(typ&7)
+}
+
+// DecodeZigZag decodes a zig-zag-encoded uint64 as an int64.
+//	Input:  {…,  5,  3,  1,  0,  2,  4,  6, …}
+//	Output: {…, -3, -2, -1,  0, +1, +2, +3, …}
+func DecodeZigZag(x uint64) int64 {
+	return int64(x>>1) ^ int64(x)<<63>>63
+}
+
+// EncodeZigZag encodes an int64 as a zig-zag-encoded uint64.
+//	Input:  {…, -3, -2, -1,  0, +1, +2, +3, …}
+//	Output: {…,  5,  3,  1,  0,  2,  4,  6, …}
+func EncodeZigZag(x int64) uint64 {
+	return uint64(x<<1) ^ uint64(x>>63)
+}
+
+// DecodeBool decodes a uint64 as a bool.
+//	Input:  {    0,    1,    2, …}
+//	Output: {false, true, true, …}
+func DecodeBool(x uint64) bool {
+	return x != 0
+}
+
+// EncodeBool encodes a bool as a uint64.
+//	Input:  {false, true}
+//	Output: {    0,    1}
+func EncodeBool(x bool) uint64 {
+	if x {
+		return 1
+	}
+	return 0
+}
diff --git a/internal/encoding/wire/wire_test.go b/internal/encoding/wire/wire_test.go
new file mode 100644
index 0000000..faf6526
--- /dev/null
+++ b/internal/encoding/wire/wire_test.go
@@ -0,0 +1,680 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package wire
+
+import (
+	"bytes"
+	"encoding/hex"
+	"io"
+	"math"
+	"strings"
+	"testing"
+)
+
+type (
+	testOps struct {
+		// appendOps is a sequence of append operations, each appending to
+		// the output of the previous append operation.
+		appendOps []appendOp
+
+		// wantRaw (if not nil) is the bytes that the appendOps should produce.
+		wantRaw []byte
+
+		// consumeOps are a sequence of consume operations, each consuming the
+		// remaining output after the previous consume operation.
+		// The first consume operation starts with the output of appendOps.
+		consumeOps []consumeOp
+	}
+
+	// appendOp represents an Append operation.
+	appendOp  = interface{}
+	appendTag struct {
+		inNum  Number
+		inType Type
+	}
+	appendVarint struct {
+		inVal uint64
+	}
+	appendFixed32 struct {
+		inVal uint32
+	}
+	appendFixed64 struct {
+		inVal uint64
+	}
+	appendBytes struct {
+		inVal []byte
+	}
+	appendGroup struct {
+		inNum Number
+		inVal []byte
+	}
+	appendRaw []byte
+
+	// consumeOp represents an Consume operation.
+	consumeOp    = interface{}
+	consumeField struct {
+		wantNum  Number
+		wantType Type
+		wantCnt  int
+		wantErr  error
+	}
+	consumeFieldValue struct {
+		inNum   Number
+		inType  Type
+		wantCnt int
+		wantErr error
+	}
+	consumeTag struct {
+		wantNum  Number
+		wantType Type
+		wantCnt  int
+		wantErr  error
+	}
+	consumeVarint struct {
+		wantVal uint64
+		wantCnt int
+		wantErr error
+	}
+	consumeFixed32 struct {
+		wantVal uint32
+		wantCnt int
+		wantErr error
+	}
+	consumeFixed64 struct {
+		wantVal uint64
+		wantCnt int
+		wantErr error
+	}
+	consumeBytes struct {
+		wantVal []byte
+		wantCnt int
+		wantErr error
+	}
+	consumeGroup struct {
+		inNum   Number
+		wantVal []byte
+		wantCnt int
+		wantErr error
+	}
+
+	ops []interface{}
+)
+
+// dhex decodes a hex-string and returns the bytes and panics if s is invalid.
+func dhex(s string) []byte {
+	b, err := hex.DecodeString(s)
+	if err != nil {
+		panic(err)
+	}
+	return b
+}
+
+func TestTag(t *testing.T) {
+	runTests(t, []testOps{{
+		appendOps:  ops{appendRaw(dhex(""))},
+		consumeOps: ops{consumeTag{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendTag{inNum: 0, inType: Fixed32Type}},
+		wantRaw:    dhex("05"),
+		consumeOps: ops{consumeTag{wantErr: errFieldNumber}},
+	}, {
+		appendOps:  ops{appendTag{inNum: 1, inType: Fixed32Type}},
+		wantRaw:    dhex("0d"),
+		consumeOps: ops{consumeTag{wantNum: 1, wantType: Fixed32Type, wantCnt: 1}},
+	}, {
+		appendOps:  ops{appendTag{inNum: FirstReservedNumber, inType: BytesType}},
+		wantRaw:    dhex("c2a309"),
+		consumeOps: ops{consumeTag{wantNum: FirstReservedNumber, wantType: BytesType, wantCnt: 3}},
+	}, {
+		appendOps:  ops{appendTag{inNum: LastReservedNumber, inType: StartGroupType}},
+		wantRaw:    dhex("fbe109"),
+		consumeOps: ops{consumeTag{wantNum: LastReservedNumber, wantType: StartGroupType, wantCnt: 3}},
+	}, {
+		appendOps:  ops{appendTag{inNum: MaxValidNumber, inType: VarintType}},
+		wantRaw:    dhex("f8ffffff0f"),
+		consumeOps: ops{consumeTag{wantNum: MaxValidNumber, wantType: VarintType, wantCnt: 5}},
+	}, {
+		appendOps:  ops{appendTag{inNum: MaxValidNumber + 1, inType: VarintType}},
+		wantRaw:    dhex("8080808010"),
+		consumeOps: ops{consumeTag{wantErr: errFieldNumber}},
+	}})
+}
+
+func TestVarint(t *testing.T) {
+	runTests(t, []testOps{{
+		appendOps:  ops{appendRaw(dhex(""))},
+		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("80"))},
+		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("8080"))},
+		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("808080"))},
+		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("80808080"))},
+		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("8080808080"))},
+		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("808080808080"))},
+		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("80808080808080"))},
+		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("8080808080808080"))},
+		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("808080808080808080"))},
+		consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("80808080808080808080"))},
+		consumeOps: ops{consumeVarint{wantErr: errOverflow}},
+	}, {
+		// Test varints at various boundaries where the length changes.
+		appendOps:  ops{appendVarint{inVal: 0x0}},
+		wantRaw:    dhex("00"),
+		consumeOps: ops{consumeVarint{wantVal: 0, wantCnt: 1}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x1}},
+		wantRaw:    dhex("01"),
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 1}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x7f}},
+		wantRaw:    dhex("7f"),
+		consumeOps: ops{consumeVarint{wantVal: 0x7f, wantCnt: 1}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x7f + 1}},
+		wantRaw:    dhex("8001"),
+		consumeOps: ops{consumeVarint{wantVal: 0x7f + 1, wantCnt: 2}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x3fff}},
+		wantRaw:    dhex("ff7f"),
+		consumeOps: ops{consumeVarint{wantVal: 0x3fff, wantCnt: 2}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x3fff + 1}},
+		wantRaw:    dhex("808001"),
+		consumeOps: ops{consumeVarint{wantVal: 0x3fff + 1, wantCnt: 3}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x1fffff}},
+		wantRaw:    dhex("ffff7f"),
+		consumeOps: ops{consumeVarint{wantVal: 0x1fffff, wantCnt: 3}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x1fffff + 1}},
+		wantRaw:    dhex("80808001"),
+		consumeOps: ops{consumeVarint{wantVal: 0x1fffff + 1, wantCnt: 4}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0xfffffff}},
+		wantRaw:    dhex("ffffff7f"),
+		consumeOps: ops{consumeVarint{wantVal: 0xfffffff, wantCnt: 4}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0xfffffff + 1}},
+		wantRaw:    dhex("8080808001"),
+		consumeOps: ops{consumeVarint{wantVal: 0xfffffff + 1, wantCnt: 5}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x7ffffffff}},
+		wantRaw:    dhex("ffffffff7f"),
+		consumeOps: ops{consumeVarint{wantVal: 0x7ffffffff, wantCnt: 5}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x7ffffffff + 1}},
+		wantRaw:    dhex("808080808001"),
+		consumeOps: ops{consumeVarint{wantVal: 0x7ffffffff + 1, wantCnt: 6}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x3ffffffffff}},
+		wantRaw:    dhex("ffffffffff7f"),
+		consumeOps: ops{consumeVarint{wantVal: 0x3ffffffffff, wantCnt: 6}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x3ffffffffff + 1}},
+		wantRaw:    dhex("80808080808001"),
+		consumeOps: ops{consumeVarint{wantVal: 0x3ffffffffff + 1, wantCnt: 7}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x1ffffffffffff}},
+		wantRaw:    dhex("ffffffffffff7f"),
+		consumeOps: ops{consumeVarint{wantVal: 0x1ffffffffffff, wantCnt: 7}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x1ffffffffffff + 1}},
+		wantRaw:    dhex("8080808080808001"),
+		consumeOps: ops{consumeVarint{wantVal: 0x1ffffffffffff + 1, wantCnt: 8}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0xffffffffffffff}},
+		wantRaw:    dhex("ffffffffffffff7f"),
+		consumeOps: ops{consumeVarint{wantVal: 0xffffffffffffff, wantCnt: 8}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0xffffffffffffff + 1}},
+		wantRaw:    dhex("808080808080808001"),
+		consumeOps: ops{consumeVarint{wantVal: 0xffffffffffffff + 1, wantCnt: 9}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x7fffffffffffffff}},
+		wantRaw:    dhex("ffffffffffffffff7f"),
+		consumeOps: ops{consumeVarint{wantVal: 0x7fffffffffffffff, wantCnt: 9}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: 0x7fffffffffffffff + 1}},
+		wantRaw:    dhex("80808080808080808001"),
+		consumeOps: ops{consumeVarint{wantVal: 0x7fffffffffffffff + 1, wantCnt: 10}},
+	}, {
+		appendOps:  ops{appendVarint{inVal: math.MaxUint64}},
+		wantRaw:    dhex("ffffffffffffffffff01"),
+		consumeOps: ops{consumeVarint{wantVal: math.MaxUint64, wantCnt: 10}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("ffffffffffffffffff02"))},
+		consumeOps: ops{consumeVarint{wantErr: errOverflow}},
+	}, {
+		// Test denormalized varints; where the encoding, while valid, is
+		// larger than necessary.
+		appendOps:  ops{appendRaw(dhex("01"))},
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 1}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("8100"))},
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 2}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("818000"))},
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 3}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("81808000"))},
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 4}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("8180808000"))},
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 5}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("818080808000"))},
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 6}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("81808080808000"))},
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 7}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("8180808080808000"))},
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 8}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("818080808080808000"))},
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 9}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("81808080808080808000"))},
+		consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 10}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("8180808080808080808000"))},
+		consumeOps: ops{consumeVarint{wantErr: errOverflow}},
+	}})
+}
+
+func TestFixed32(t *testing.T) {
+	runTests(t, []testOps{{
+		appendOps:  ops{appendRaw(dhex(""))},
+		consumeOps: ops{consumeFixed32{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("000000"))},
+		consumeOps: ops{consumeFixed32{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendFixed32{0}},
+		wantRaw:    dhex("00000000"),
+		consumeOps: ops{consumeFixed32{wantVal: 0, wantCnt: 4}},
+	}, {
+		appendOps:  ops{appendFixed32{math.MaxUint32}},
+		wantRaw:    dhex("ffffffff"),
+		consumeOps: ops{consumeFixed32{wantVal: math.MaxUint32, wantCnt: 4}},
+	}, {
+		appendOps:  ops{appendFixed32{0xf0e1d2c3}},
+		wantRaw:    dhex("c3d2e1f0"),
+		consumeOps: ops{consumeFixed32{wantVal: 0xf0e1d2c3, wantCnt: 4}},
+	}})
+}
+
+func TestFixed64(t *testing.T) {
+	runTests(t, []testOps{{
+		appendOps:  ops{appendRaw(dhex(""))},
+		consumeOps: ops{consumeFixed64{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("00000000000000"))},
+		consumeOps: ops{consumeFixed64{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendFixed64{0}},
+		wantRaw:    dhex("0000000000000000"),
+		consumeOps: ops{consumeFixed64{wantVal: 0, wantCnt: 8}},
+	}, {
+		appendOps:  ops{appendFixed64{math.MaxUint64}},
+		wantRaw:    dhex("ffffffffffffffff"),
+		consumeOps: ops{consumeFixed64{wantVal: math.MaxUint64, wantCnt: 8}},
+	}, {
+		appendOps:  ops{appendFixed64{0xf0e1d2c3b4a59687}},
+		wantRaw:    dhex("8796a5b4c3d2e1f0"),
+		consumeOps: ops{consumeFixed64{wantVal: 0xf0e1d2c3b4a59687, wantCnt: 8}},
+	}, {
+		appendOps:  ops{appendRaw(dhex(""))},
+		consumeOps: ops{consumeBytes{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendRaw(dhex("01"))},
+		consumeOps: ops{consumeBytes{wantErr: io.ErrUnexpectedEOF}},
+	}})
+}
+
+func TestBytes(t *testing.T) {
+	runTests(t, []testOps{{
+		appendOps:  ops{appendVarint{0}, appendRaw("")},
+		wantRaw:    dhex("00"),
+		consumeOps: ops{consumeBytes{wantVal: dhex(""), wantCnt: 1}},
+	}, {
+		appendOps:  ops{appendBytes{[]byte("hello")}},
+		wantRaw:    []byte("\x05hello"),
+		consumeOps: ops{consumeBytes{wantVal: []byte("hello"), wantCnt: 6}},
+	}, {
+		appendOps:  ops{appendBytes{[]byte(strings.Repeat("hello", 50))}},
+		wantRaw:    []byte("\xfa\x01" + strings.Repeat("hello", 50)),
+		consumeOps: ops{consumeBytes{wantVal: []byte(strings.Repeat("hello", 50)), wantCnt: 252}},
+	}, {
+		appendOps:  ops{appendRaw("\x85\x80\x00hello")},
+		consumeOps: ops{consumeBytes{wantVal: []byte("hello"), wantCnt: 8}},
+	}, {
+		appendOps:  ops{appendRaw("\x85\x80\x00hell")},
+		consumeOps: ops{consumeBytes{wantErr: io.ErrUnexpectedEOF}},
+	}})
+}
+
+func TestGroup(t *testing.T) {
+	runTests(t, []testOps{{
+		appendOps:  ops{appendRaw(dhex(""))},
+		consumeOps: ops{consumeGroup{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps:  ops{appendTag{inNum: 0, inType: StartGroupType}},
+		consumeOps: ops{consumeGroup{inNum: 1, wantErr: errFieldNumber}},
+	}, {
+		appendOps:  ops{appendTag{inNum: 2, inType: EndGroupType}},
+		consumeOps: ops{consumeGroup{inNum: 1, wantErr: errEndGroup}},
+	}, {
+		appendOps:  ops{appendTag{inNum: 1, inType: EndGroupType}},
+		consumeOps: ops{consumeGroup{inNum: 1, wantCnt: 1}},
+	}, {
+		appendOps: ops{
+			appendTag{inNum: 5, inType: Fixed32Type},
+			appendFixed32{0xf0e1d2c3},
+			appendTag{inNum: 5, inType: EndGroupType},
+		},
+		wantRaw:    dhex("2dc3d2e1f02c"),
+		consumeOps: ops{consumeGroup{inNum: 5, wantVal: dhex("2dc3d2e1f0"), wantCnt: 6}},
+	}, {
+		appendOps: ops{
+			appendTag{inNum: 5, inType: Fixed32Type},
+			appendFixed32{0xf0e1d2c3},
+			appendRaw(dhex("ac808000")),
+		},
+		consumeOps: ops{consumeGroup{inNum: 5, wantVal: dhex("2dc3d2e1f0"), wantCnt: 9}},
+	}})
+}
+
+func TestField(t *testing.T) {
+	runTests(t, []testOps{{
+		appendOps:  ops{appendRaw(dhex(""))},
+		consumeOps: ops{consumeField{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps: ops{
+			appendTag{inNum: 5000, inType: StartGroupType},
+			appendTag{inNum: 1, inType: VarintType},
+			appendVarint{123456789},
+			appendTag{inNum: 12, inType: Fixed32Type},
+			appendFixed32{123456789},
+			appendTag{inNum: 123, inType: Fixed64Type},
+			appendFixed64{123456789},
+			appendTag{inNum: 1234, inType: BytesType},
+			appendBytes{[]byte("hello")},
+			appendTag{inNum: 12345, inType: StartGroupType},
+			appendTag{inNum: 11, inType: VarintType},
+			appendVarint{123456789},
+			appendTag{inNum: 1212, inType: Fixed32Type},
+			appendFixed32{123456789},
+			appendTag{inNum: 123123, inType: Fixed64Type},
+			appendFixed64{123456789},
+			appendTag{inNum: 12341234, inType: BytesType},
+			appendBytes{[]byte("goodbye")},
+			appendTag{inNum: 12345, inType: EndGroupType},
+			appendTag{inNum: 5000, inType: EndGroupType},
+		},
+		wantRaw: dhex("c3b80208959aef3a6515cd5b07d90715cd5b0700000000924d0568656c6c6fcb830658959aef3ae54b15cd5b07998f3c15cd5b070000000092ff892f07676f6f64627965cc8306c4b802"),
+		consumeOps: ops{
+			consumeTag{wantNum: 5000, wantType: StartGroupType, wantCnt: 3},
+			consumeTag{wantNum: 1, wantType: VarintType, wantCnt: 1},
+			consumeVarint{wantVal: 123456789, wantCnt: 4},
+			consumeTag{wantNum: 12, wantType: Fixed32Type, wantCnt: 1},
+			consumeFixed32{wantVal: 123456789, wantCnt: 4},
+			consumeTag{wantNum: 123, wantType: Fixed64Type, wantCnt: 2},
+			consumeFixed64{wantVal: 123456789, wantCnt: 8},
+			consumeTag{wantNum: 1234, wantType: BytesType, wantCnt: 2},
+			consumeBytes{wantVal: []byte("hello"), wantCnt: 6},
+			consumeTag{wantNum: 12345, wantType: StartGroupType, wantCnt: 3},
+			consumeTag{wantNum: 11, wantType: VarintType, wantCnt: 1},
+			consumeVarint{wantVal: 123456789, wantCnt: 4},
+			consumeTag{wantNum: 1212, wantType: Fixed32Type, wantCnt: 2},
+			consumeFixed32{wantVal: 123456789, wantCnt: 4},
+			consumeTag{wantNum: 123123, wantType: Fixed64Type, wantCnt: 3},
+			consumeFixed64{wantVal: 123456789, wantCnt: 8},
+			consumeTag{wantNum: 12341234, wantType: BytesType, wantCnt: 4},
+			consumeBytes{wantVal: []byte("goodbye"), wantCnt: 8},
+			consumeTag{wantNum: 12345, wantType: EndGroupType, wantCnt: 3},
+			consumeTag{wantNum: 5000, wantType: EndGroupType, wantCnt: 3},
+		},
+	}, {
+		appendOps:  ops{appendRaw(dhex("c3b80208959aef3a6515cd5b07d90715cd5b0700000000924d0568656c6c6fcb830658959aef3ae54b15cd5b07998f3c15cd5b070000000092ff892f07676f6f64627965cc8306c4b802"))},
+		consumeOps: ops{consumeField{wantNum: 5000, wantType: StartGroupType, wantCnt: 74}},
+	}, {
+		appendOps:  ops{appendTag{inNum: 5, inType: EndGroupType}},
+		wantRaw:    dhex("2c"),
+		consumeOps: ops{consumeField{wantErr: errEndGroup}},
+	}, {
+		appendOps: ops{
+			appendTag{inNum: 1, inType: StartGroupType},
+			appendTag{inNum: 22, inType: StartGroupType},
+			appendTag{inNum: 333, inType: StartGroupType},
+			appendTag{inNum: 4444, inType: StartGroupType},
+			appendTag{inNum: 4444, inType: EndGroupType},
+			appendTag{inNum: 333, inType: EndGroupType},
+			appendTag{inNum: 22, inType: EndGroupType},
+			appendTag{inNum: 1, inType: EndGroupType},
+		},
+		wantRaw:    dhex("0bb301eb14e39502e49502ec14b4010c"),
+		consumeOps: ops{consumeField{wantNum: 1, wantType: StartGroupType, wantCnt: 16}},
+	}, {
+		appendOps: ops{
+			appendTag{inNum: 1, inType: StartGroupType},
+			appendGroup{inNum: 1, inVal: dhex("b301eb14e39502e49502ec14b401")},
+		},
+		wantRaw:    dhex("0b" + "b301eb14e39502e49502ec14b401" + "0c"),
+		consumeOps: ops{consumeField{wantNum: 1, wantType: StartGroupType, wantCnt: 16}},
+	}, {
+		appendOps: ops{
+			appendTag{inNum: 1, inType: StartGroupType},
+			appendTag{inNum: 22, inType: StartGroupType},
+			appendTag{inNum: 333, inType: StartGroupType},
+			appendTag{inNum: 4444, inType: StartGroupType},
+			appendTag{inNum: 333, inType: EndGroupType},
+			appendTag{inNum: 22, inType: EndGroupType},
+			appendTag{inNum: 1, inType: EndGroupType},
+		},
+		consumeOps: ops{consumeField{wantErr: errEndGroup}},
+	}, {
+		appendOps: ops{
+			appendTag{inNum: 1, inType: StartGroupType},
+			appendTag{inNum: 22, inType: StartGroupType},
+			appendTag{inNum: 333, inType: StartGroupType},
+			appendTag{inNum: 4444, inType: StartGroupType},
+			appendTag{inNum: 4444, inType: EndGroupType},
+			appendTag{inNum: 333, inType: EndGroupType},
+			appendTag{inNum: 22, inType: EndGroupType},
+		},
+		consumeOps: ops{consumeField{wantErr: io.ErrUnexpectedEOF}},
+	}, {
+		appendOps: ops{
+			appendTag{inNum: 1, inType: StartGroupType},
+			appendTag{inNum: 22, inType: StartGroupType},
+			appendTag{inNum: 333, inType: StartGroupType},
+			appendTag{inNum: 4444, inType: StartGroupType},
+			appendTag{inNum: 0, inType: VarintType},
+			appendTag{inNum: 4444, inType: EndGroupType},
+			appendTag{inNum: 333, inType: EndGroupType},
+			appendTag{inNum: 22, inType: EndGroupType},
+			appendTag{inNum: 1, inType: EndGroupType},
+		},
+		consumeOps: ops{consumeField{wantErr: errFieldNumber}},
+	}, {
+		appendOps: ops{
+			appendTag{inNum: 1, inType: StartGroupType},
+			appendTag{inNum: 22, inType: StartGroupType},
+			appendTag{inNum: 333, inType: StartGroupType},
+			appendTag{inNum: 4444, inType: StartGroupType},
+			appendTag{inNum: 1, inType: 6},
+			appendTag{inNum: 4444, inType: EndGroupType},
+			appendTag{inNum: 333, inType: EndGroupType},
+			appendTag{inNum: 22, inType: EndGroupType},
+			appendTag{inNum: 1, inType: EndGroupType},
+		},
+		consumeOps: ops{consumeField{wantErr: errReserved}},
+	}})
+}
+
+func runTests(t *testing.T, tests []testOps) {
+	for _, tt := range tests {
+		t.Run("", func(t *testing.T) {
+			var b []byte
+			for _, op := range tt.appendOps {
+				b0 := b
+				switch op := op.(type) {
+				case appendTag:
+					b = AppendTag(b, op.inNum, op.inType)
+				case appendVarint:
+					b = AppendVarint(b, op.inVal)
+				case appendFixed32:
+					b = AppendFixed32(b, op.inVal)
+				case appendFixed64:
+					b = AppendFixed64(b, op.inVal)
+				case appendBytes:
+					b = AppendBytes(b, op.inVal)
+				case appendGroup:
+					b = AppendGroup(b, op.inNum, op.inVal)
+				case appendRaw:
+					b = append(b, op...)
+				}
+
+				check := func(label string, want int) {
+					t.Helper()
+					if got := len(b) - len(b0); got != want {
+						t.Errorf("len(Append%v) and Size%v mismatch: got %v, want %v", label, label, got, want)
+					}
+				}
+				switch op := op.(type) {
+				case appendTag:
+					check("Tag", SizeTag(op.inNum))
+				case appendVarint:
+					check("Varint", SizeVarint(op.inVal))
+				case appendFixed32:
+					check("Fixed32", SizeFixed32())
+				case appendFixed64:
+					check("Fixed64", SizeFixed64())
+				case appendBytes:
+					check("Bytes", SizeBytes(len(op.inVal)))
+				case appendGroup:
+					check("Group", SizeGroup(op.inNum, len(op.inVal)))
+				}
+			}
+
+			if tt.wantRaw != nil && !bytes.Equal(b, tt.wantRaw) {
+				t.Errorf("raw output mismatch:\ngot  %x\nwant %x", b, tt.wantRaw)
+			}
+
+			for _, op := range tt.consumeOps {
+				check := func(label string, gotCnt, wantCnt int, wantErr error) {
+					t.Helper()
+					gotErr := ParseError(gotCnt)
+					if gotCnt < 0 {
+						gotCnt = 0
+					}
+					if gotCnt != wantCnt {
+						t.Errorf("Consume%v(): consumed %d bytes, want %d bytes consumed", label, gotCnt, wantCnt)
+					}
+					if gotErr != wantErr {
+						t.Errorf("Consume%v(): got %v error, want %v error", label, gotErr, wantErr)
+					}
+					b = b[gotCnt:]
+				}
+				switch op := op.(type) {
+				case consumeField:
+					gotNum, gotType, n := ConsumeField(b)
+					if gotNum != op.wantNum || gotType != op.wantType {
+						t.Errorf("ConsumeField() = (%d, %v), want (%d, %v)", gotNum, gotType, op.wantNum, op.wantType)
+					}
+					check("Field", n, op.wantCnt, op.wantErr)
+				case consumeFieldValue:
+					n := ConsumeFieldValue(op.inNum, op.inType, b)
+					check("FieldValue", n, op.wantCnt, op.wantErr)
+				case consumeTag:
+					gotNum, gotType, n := ConsumeTag(b)
+					if gotNum != op.wantNum || gotType != op.wantType {
+						t.Errorf("ConsumeTag() = (%d, %v), want (%d, %v)", gotNum, gotType, op.wantNum, op.wantType)
+					}
+					check("Tag", n, op.wantCnt, op.wantErr)
+				case consumeVarint:
+					gotVal, n := ConsumeVarint(b)
+					if gotVal != op.wantVal {
+						t.Errorf("ConsumeVarint() = %d, want %d", gotVal, op.wantVal)
+					}
+					check("Varint", n, op.wantCnt, op.wantErr)
+				case consumeFixed32:
+					gotVal, n := ConsumeFixed32(b)
+					if gotVal != op.wantVal {
+						t.Errorf("ConsumeFixed32() = %d, want %d", gotVal, op.wantVal)
+					}
+					check("Fixed32", n, op.wantCnt, op.wantErr)
+				case consumeFixed64:
+					gotVal, n := ConsumeFixed64(b)
+					if gotVal != op.wantVal {
+						t.Errorf("ConsumeFixed64() = %d, want %d", gotVal, op.wantVal)
+					}
+					check("Fixed64", n, op.wantCnt, op.wantErr)
+				case consumeBytes:
+					gotVal, n := ConsumeBytes(b)
+					if !bytes.Equal(gotVal, op.wantVal) {
+						t.Errorf("ConsumeBytes() = %x, want %x", gotVal, op.wantVal)
+					}
+					check("Bytes", n, op.wantCnt, op.wantErr)
+				case consumeGroup:
+					gotVal, n := ConsumeGroup(op.inNum, b)
+					if !bytes.Equal(gotVal, op.wantVal) {
+						t.Errorf("ConsumeGroup() = %x, want %x", gotVal, op.wantVal)
+					}
+					check("Group", n, op.wantCnt, op.wantErr)
+				}
+			}
+		})
+	}
+}
+
+func TestZigZag(t *testing.T) {
+	tests := []struct {
+		dec int64
+		enc uint64
+	}{
+		{math.MinInt64 + 0, math.MaxUint64 - 0},
+		{math.MinInt64 + 1, math.MaxUint64 - 2},
+		{math.MinInt64 + 2, math.MaxUint64 - 4},
+		{-3, 5},
+		{-2, 3},
+		{-1, 1},
+		{0, 0},
+		{+1, 2},
+		{+2, 4},
+		{+3, 6},
+		{math.MaxInt64 - 2, math.MaxUint64 - 5},
+		{math.MaxInt64 - 1, math.MaxUint64 - 3},
+		{math.MaxInt64 - 0, math.MaxUint64 - 1},
+	}
+
+	for _, tt := range tests {
+		if enc := EncodeZigZag(tt.dec); enc != tt.enc {
+			t.Errorf("EncodeZigZag(%d) = %d, want %d", tt.dec, enc, tt.enc)
+		}
+		if dec := DecodeZigZag(tt.enc); dec != tt.dec {
+			t.Errorf("DecodeZigZag(%d) = %d, want %d", tt.enc, dec, tt.dec)
+		}
+	}
+}