internal/encoding/pack: initial commit
Package pack enables manual encoding and decoding of protobuf wire data.
This package is intended only for testing and debugging purposes.
Message.Marshal is useful for hand-crafting raw wire testdata in tests
in a readable form.
Message.Unmarshal is useful for parsing raw wire data for debugging.
For that reason, effort was put into trying to get its string formatted
output look humanly readable.
High-level API:
type Number = wire.Number
const MinValidNumber Number = wire.MinValidNumber ...
type Type = wire.Type
const VarintType Type = wire.VarintType ...
type Token token
type Tag struct { ... }
type Bool bool
type Varint int64
type Svarint int64
type Uvarint uint64
type Int32 int32
type Uint32 uint32
type Float32 float32
type Int64 int64
type Uint64 uint64
type Float64 float64
type String string
type Bytes []byte
type LengthPrefix Message
type Denormalized struct { ... }
type Raw []byte
type Message []Token
func (Message) Size() int
func (Message) Marshal() []byte
func (*Message) Unmarshal(in []byte)
func (*Message) UnmarshalDescriptor(in []byte, desc protoreflect.MessageDescriptor)
func (Message) Format(s fmt.State, r rune)
Change-Id: Id99b340971a09c8a040838b155782a5d32b548bc
Reviewed-on: https://go-review.googlesource.com/129404
Reviewed-by: Herbie Ong <herbie@google.com>
diff --git a/internal/encoding/pack/pack.go b/internal/encoding/pack/pack.go
new file mode 100644
index 0000000..89601ba
--- /dev/null
+++ b/internal/encoding/pack/pack.go
@@ -0,0 +1,681 @@
+// 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 pack enables manual encoding and decoding of protobuf wire data.
+//
+// This package is intended for use in debugging and/or creation of test data.
+// Proper usage of this package requires knowledge of the wire format.
+//
+// See https://developers.google.com/protocol-buffers/docs/encoding.
+package pack
+
+import (
+ "fmt"
+ "io"
+ "math"
+ "path"
+ "reflect"
+ "strconv"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+
+ "google.golang.org/proto/internal/encoding/wire"
+ "google.golang.org/proto/reflect/protoreflect"
+)
+
+// Number is the field number; aliased from the wire package for convenience.
+type Number = wire.Number
+
+// Number type constants; copied from the wire package for convenience.
+const (
+ MinValidNumber Number = wire.MinValidNumber
+ FirstReservedNumber Number = wire.FirstReservedNumber
+ LastReservedNumber Number = wire.LastReservedNumber
+ MaxValidNumber Number = wire.MaxValidNumber
+)
+
+// Type is the wire type; aliased from the wire package for convenience.
+type Type = wire.Type
+
+// Wire type constants; copied from the wire package for convenience.
+const (
+ VarintType Type = wire.VarintType
+ Fixed32Type Type = wire.Fixed32Type
+ Fixed64Type Type = wire.Fixed64Type
+ BytesType Type = wire.BytesType
+ StartGroupType Type = wire.StartGroupType
+ EndGroupType Type = wire.EndGroupType
+)
+
+type (
+ // Token is any other type (e.g., Message, Tag, Varint, Float32, etc).
+ Token token
+ // Message is a ordered sequence Tokens.
+ Message []Token
+
+ // Tag is a tuple of the field number and the wire type.
+ Tag struct {
+ Number Number
+ Type Type
+ }
+ // Bool is a boolean.
+ Bool bool
+ // Varint is a signed varint using 64-bit two's complement encoding.
+ Varint int64
+ // Svarint is a signed varint using zig-zag encoding.
+ Svarint int64
+ // Uvarint is a unsigned varint.
+ Uvarint uint64
+
+ // Int32 is a signed 32-bit fixed-width integer.
+ Int32 int32
+ // Uint32 is an unsigned 32-bit fixed-width integer.
+ Uint32 uint32
+ // Float32 is a 32-bit fixed-width floating point number.
+ Float32 float32
+
+ // Int64 is a signed 64-bit fixed-width integer.
+ Int64 int64
+ // Uint64 is an unsigned 64-bit fixed-width integer.
+ Uint64 uint64
+ // Float64 is a 64-bit fixed-width floating point number.
+ Float64 float64
+
+ // String is a length-prefixed string.
+ String string
+ // Bytes is a length-prefixed bytes.
+ Bytes []byte
+ // LengthPrefix is a length-prefixed message.
+ LengthPrefix Message
+
+ // Denormalized is a denormalized varint value, where a varint is encoded
+ // using more bytes than is strictly necessary. The number of extra bytes
+ // alone is sufficient to losslessly represent the denormalized varint.
+ //
+ // The value may be one of Tag, Bool, Varint, Svarint, or Uvarint,
+ // where the varint representation of each token is denormalized.
+ //
+ // Alternatively, the value may be one of String, Bytes, or LengthPrefix,
+ // where the varint representation of the length-prefix is denormalized.
+ Denormalized struct {
+ Count uint // number of extra bytes
+ Value Token
+ }
+
+ // Raw are bytes directly appended to output.
+ Raw []byte
+)
+
+type token interface {
+ isToken()
+}
+
+func (Message) isToken() {}
+func (Tag) isToken() {}
+func (Bool) isToken() {}
+func (Varint) isToken() {}
+func (Svarint) isToken() {}
+func (Uvarint) isToken() {}
+func (Int32) isToken() {}
+func (Uint32) isToken() {}
+func (Float32) isToken() {}
+func (Int64) isToken() {}
+func (Uint64) isToken() {}
+func (Float64) isToken() {}
+func (String) isToken() {}
+func (Bytes) isToken() {}
+func (LengthPrefix) isToken() {}
+func (Denormalized) isToken() {}
+func (Raw) isToken() {}
+
+// Size reports the size in bytes of the marshaled message.
+func (m Message) Size() int {
+ var n int
+ for _, v := range m {
+ switch v := v.(type) {
+ case Message:
+ n += v.Size()
+ case Tag:
+ n += wire.SizeTag(v.Number)
+ case Bool:
+ n += wire.SizeVarint(wire.EncodeBool(false))
+ case Varint:
+ n += wire.SizeVarint(uint64(v))
+ case Svarint:
+ n += wire.SizeVarint(wire.EncodeZigZag(int64(v)))
+ case Uvarint:
+ n += wire.SizeVarint(uint64(v))
+ case Int32, Uint32, Float32:
+ n += wire.SizeFixed32()
+ case Int64, Uint64, Float64:
+ n += wire.SizeFixed64()
+ case String:
+ n += wire.SizeBytes(len(v))
+ case Bytes:
+ n += wire.SizeBytes(len(v))
+ case LengthPrefix:
+ n += wire.SizeBytes(Message(v).Size())
+ case Denormalized:
+ n += int(v.Count) + Message{v.Value}.Size()
+ case Raw:
+ n += len(v)
+ default:
+ panic(fmt.Sprintf("unknown type: %T", v))
+ }
+ }
+ return n
+}
+
+// Message encodes an AST into the protobuf wire format.
+//
+// Example message definition:
+// message MyMessage {
+// string field1 = 1;
+// int64 field2 = 2;
+// repeated float32 field3 = 3;
+// }
+//
+// Example encoded message:
+// b := Message{
+// Tag{1, BytesType}, String("Hello, world!"),
+// Tag{2, VarintType}, Varint(-10),
+// Tag{3, BytesType}, LengthPrefix{
+// Float32(1.1), Float32(2.2), Float32(3.3),
+// },
+// }.Marshal()
+//
+// Resulting wire data:
+// 0x0000 0a 0d 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 10 |..Hello, world!.|
+// 0x0010 f6 ff ff ff ff ff ff ff ff 01 1a 0c cd cc 8c 3f |...............?|
+// 0x0020 cd cc 0c 40 33 33 53 40 |...@33S@|
+func (m Message) Marshal() []byte {
+ var out []byte
+ for _, v := range m {
+ switch v := v.(type) {
+ case Message:
+ out = append(out, v.Marshal()...)
+ case Tag:
+ out = wire.AppendTag(out, v.Number, v.Type)
+ case Bool:
+ out = wire.AppendVarint(out, wire.EncodeBool(bool(v)))
+ case Varint:
+ out = wire.AppendVarint(out, uint64(v))
+ case Svarint:
+ out = wire.AppendVarint(out, wire.EncodeZigZag(int64(v)))
+ case Uvarint:
+ out = wire.AppendVarint(out, uint64(v))
+ case Int32:
+ out = wire.AppendFixed32(out, uint32(v))
+ case Uint32:
+ out = wire.AppendFixed32(out, uint32(v))
+ case Float32:
+ out = wire.AppendFixed32(out, math.Float32bits(float32(v)))
+ case Int64:
+ out = wire.AppendFixed64(out, uint64(v))
+ case Uint64:
+ out = wire.AppendFixed64(out, uint64(v))
+ case Float64:
+ out = wire.AppendFixed64(out, math.Float64bits(float64(v)))
+ case String:
+ out = wire.AppendBytes(out, []byte(v))
+ case Bytes:
+ out = wire.AppendBytes(out, []byte(v))
+ case LengthPrefix:
+ out = wire.AppendBytes(out, Message(v).Marshal())
+ case Denormalized:
+ b := Message{v.Value}.Marshal()
+ _, n := wire.ConsumeVarint(b)
+ out = append(out, b[:n]...)
+ for i := uint(0); i < v.Count; i++ {
+ out[len(out)-1] |= 0x80 // set continuation bit on previous
+ out = append(out, 0)
+ }
+ out = append(out, b[n:]...)
+ case Raw:
+ return append(out, v...)
+ default:
+ panic(fmt.Sprintf("unknown type: %T", v))
+ }
+ }
+ return out
+}
+
+// Unmarshal parses the input protobuf wire data as a Message AST.
+// Any parsing error results in the remainder of the input being
+// concatenated to the message as a Raw type.
+//
+// Each tag (a tuple of the field number and wire type) encountered is
+// appended to the AST as a Tag.
+//
+// The contents of each wire type is mapped to the following Go types:
+// VarintType => Uvarint
+// Fixed32Type => Uint32
+// Fixed64Type => Uint64
+// BytesType => Bytes
+// GroupType => Message
+//
+// Since the wire format is not self-describing, this function cannot parse
+// sub-messages and will leave them as the Bytes type. Further manual parsing
+// can be performed as such:
+// var m, m1, m2 Message
+// m.Unmarshal(b)
+// m1.Unmarshal(m[3].(Bytes))
+// m[3] = LengthPrefix(m1)
+// m2.Unmarshal(m[3].(LengthPrefix)[1].(Bytes))
+// m[3].(LengthPrefix)[1] = LengthPrefix(m2)
+//
+// Unmarshal is useful for debugging the protobuf wire format.
+func (m *Message) Unmarshal(in []byte) {
+ m.UnmarshalDescriptor(in, nil)
+}
+
+// UnmarshalDescriptor parses the input protobuf wire data as a Message AST
+// using the provided message descriptor for more accurate parsing of fields.
+// It operates like Unmarshal, but may use a wider range of Go types to
+// represent the wire data.
+//
+// The contents of each wire type is mapped to one of the following Go types:
+// VarintType => Bool, Varint, Svarint, Uvarint
+// Fixed32Type => Int32, Uint32, Float32
+// Fixed64Type => Uint32, Uint64, Float64
+// BytesType => String, Bytes, LengthPrefix
+// GroupType => Message
+//
+// If the field is unknown, it uses the same mapping as Unmarshal.
+// Known sub-messages are parsed as a Message and packed repeated fields are
+// parsed as a LengthPrefix.
+func (m *Message) UnmarshalDescriptor(in []byte, desc protoreflect.MessageDescriptor) {
+ p := parser{in: in, out: *m}
+ p.parseMessage(desc, false)
+ *m = p.out
+}
+
+type parser struct {
+ in []byte
+ out []Token
+}
+
+func (p *parser) parseMessage(msgDesc protoreflect.MessageDescriptor, group bool) {
+ for len(p.in) > 0 {
+ v, n := wire.ConsumeVarint(p.in)
+ num, typ := wire.DecodeTag(v)
+ if n < 0 || num < 0 || v > math.MaxUint32 {
+ p.out, p.in = append(p.out, Raw(p.in)), nil
+ return
+ }
+ if typ == EndGroupType && group {
+ return // if inside a group, then stop
+ }
+ p.out, p.in = append(p.out, Tag{num, typ}), p.in[n:]
+ if m := n - wire.SizeVarint(v); m > 0 {
+ p.out[len(p.out)-1] = Denormalized{uint(m), p.out[len(p.out)-1]}
+ }
+
+ // If descriptor is available, use it for more accurate parsing.
+ var isPacked bool
+ var kind protoreflect.Kind
+ var subDesc protoreflect.MessageDescriptor
+ if msgDesc != nil && !msgDesc.IsPlaceholder() {
+ if fieldDesc := msgDesc.Fields().ByNumber(num); fieldDesc != nil {
+ isPacked = fieldDesc.IsPacked()
+ kind = fieldDesc.Kind()
+ switch kind {
+ case protoreflect.MessageKind, protoreflect.GroupKind:
+ subDesc = fieldDesc.MessageType()
+ if subDesc == nil || subDesc.IsPlaceholder() {
+ kind = 0
+ }
+ }
+ }
+ }
+
+ switch typ {
+ case VarintType:
+ p.parseVarint(kind)
+ case Fixed32Type:
+ p.parseFixed32(kind)
+ case Fixed64Type:
+ p.parseFixed64(kind)
+ case BytesType:
+ p.parseBytes(isPacked, kind, subDesc)
+ case StartGroupType:
+ p.parseGroup(subDesc)
+ case EndGroupType:
+ // Handled above.
+ default:
+ p.out, p.in = append(p.out, Raw(p.in)), nil
+ }
+ }
+}
+
+func (p *parser) parseVarint(kind protoreflect.Kind) {
+ v, n := wire.ConsumeVarint(p.in)
+ if n < 0 {
+ p.out, p.in = append(p.out, Raw(p.in)), nil
+ return
+ }
+ switch kind {
+ case protoreflect.BoolKind:
+ switch v {
+ case 0:
+ p.out, p.in = append(p.out, Bool(false)), p.in[n:]
+ case 1:
+ p.out, p.in = append(p.out, Bool(true)), p.in[n:]
+ default:
+ p.out, p.in = append(p.out, Uvarint(v)), p.in[n:]
+ }
+ case protoreflect.Int32Kind, protoreflect.Int64Kind:
+ p.out, p.in = append(p.out, Varint(v)), p.in[n:]
+ case protoreflect.Sint32Kind, protoreflect.Sint64Kind:
+ p.out, p.in = append(p.out, Svarint(wire.DecodeZigZag(v))), p.in[n:]
+ default:
+ p.out, p.in = append(p.out, Uvarint(v)), p.in[n:]
+ }
+ if m := n - wire.SizeVarint(v); m > 0 {
+ p.out[len(p.out)-1] = Denormalized{uint(m), p.out[len(p.out)-1]}
+ }
+}
+
+func (p *parser) parseFixed32(kind protoreflect.Kind) {
+ v, n := wire.ConsumeFixed32(p.in)
+ if n < 0 {
+ p.out, p.in = append(p.out, Raw(p.in)), nil
+ return
+ }
+ switch kind {
+ case protoreflect.FloatKind:
+ p.out, p.in = append(p.out, Float32(math.Float32frombits(v))), p.in[n:]
+ case protoreflect.Sfixed32Kind:
+ p.out, p.in = append(p.out, Int32(v)), p.in[n:]
+ default:
+ p.out, p.in = append(p.out, Uint32(v)), p.in[n:]
+ }
+}
+
+func (p *parser) parseFixed64(kind protoreflect.Kind) {
+ v, n := wire.ConsumeFixed64(p.in)
+ if n < 0 {
+ p.out, p.in = append(p.out, Raw(p.in)), nil
+ return
+ }
+ switch kind {
+ case protoreflect.DoubleKind:
+ p.out, p.in = append(p.out, Float64(math.Float64frombits(v))), p.in[n:]
+ case protoreflect.Sfixed64Kind:
+ p.out, p.in = append(p.out, Int64(v)), p.in[n:]
+ default:
+ p.out, p.in = append(p.out, Uint64(v)), p.in[n:]
+ }
+}
+
+func (p *parser) parseBytes(isPacked bool, kind protoreflect.Kind, desc protoreflect.MessageDescriptor) {
+ v, n := wire.ConsumeVarint(p.in)
+ if n < 0 {
+ p.out, p.in = append(p.out, Raw(p.in)), nil
+ return
+ }
+ p.out, p.in = append(p.out, Uvarint(v)), p.in[n:]
+ if m := n - wire.SizeVarint(v); m > 0 {
+ p.out[len(p.out)-1] = Denormalized{uint(m), p.out[len(p.out)-1]}
+ }
+ if v > uint64(len(p.in)) {
+ p.out, p.in = append(p.out, Raw(p.in)), nil
+ return
+ }
+ p.out = p.out[:len(p.out)-1] // subsequent tokens contain prefix-length
+
+ if isPacked {
+ p.parsePacked(int(v), kind)
+ } else {
+ switch kind {
+ case protoreflect.MessageKind:
+ p2 := parser{in: p.in[:v]}
+ p2.parseMessage(desc, false)
+ p.out, p.in = append(p.out, LengthPrefix(p2.out)), p.in[v:]
+ case protoreflect.StringKind:
+ p.out, p.in = append(p.out, String(p.in[:v])), p.in[v:]
+ default:
+ p.out, p.in = append(p.out, Bytes(p.in[:v])), p.in[v:]
+ }
+ }
+ if m := n - wire.SizeVarint(v); m > 0 {
+ p.out[len(p.out)-1] = Denormalized{uint(m), p.out[len(p.out)-1]}
+ }
+}
+
+func (p *parser) parsePacked(n int, kind protoreflect.Kind) {
+ p2 := parser{in: p.in[:n]}
+ for len(p2.in) > 0 {
+ switch kind {
+ case protoreflect.BoolKind, protoreflect.EnumKind,
+ protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Uint32Kind,
+ protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Uint64Kind:
+ p2.parseVarint(kind)
+ case protoreflect.Fixed32Kind, protoreflect.Sfixed32Kind, protoreflect.FloatKind:
+ p2.parseFixed32(kind)
+ case protoreflect.Fixed64Kind, protoreflect.Sfixed64Kind, protoreflect.DoubleKind:
+ p2.parseFixed64(kind)
+ default:
+ panic(fmt.Sprintf("invalid packed kind: %v", kind))
+ }
+ }
+ p.out, p.in = append(p.out, LengthPrefix(p2.out)), p.in[n:]
+}
+
+func (p *parser) parseGroup(desc protoreflect.MessageDescriptor) {
+ p2 := parser{in: p.in}
+ p2.parseMessage(desc, true)
+ if len(p2.out) > 0 {
+ p.out = append(p.out, Message(p2.out))
+ }
+ p.in = p2.in
+
+ // Append the trailing end group.
+ v, n := wire.ConsumeVarint(p.in)
+ if num, typ := wire.DecodeTag(v); typ == EndGroupType {
+ p.out, p.in = append(p.out, Tag{num, typ}), p.in[n:]
+ if m := n - wire.SizeVarint(v); m > 0 {
+ p.out[len(p.out)-1] = Denormalized{uint(m), p.out[len(p.out)-1]}
+ }
+ }
+}
+
+// Format implements a custom formatter to visualize the Message AST.
+// Using "%#v" formats the Message in Go source code.
+func (m Message) Format(s fmt.State, r rune) {
+ switch r {
+ case 'x':
+ io.WriteString(s, fmt.Sprintf("%x", m.Marshal()))
+ case 'X':
+ io.WriteString(s, fmt.Sprintf("%X", m.Marshal()))
+ case 'v':
+ switch {
+ case s.Flag('#'):
+ io.WriteString(s, m.format(true, true))
+ case s.Flag('+'):
+ io.WriteString(s, m.format(false, true))
+ default:
+ io.WriteString(s, m.format(false, false))
+ }
+ default:
+ panic("invalid verb: " + string(r))
+ }
+}
+
+// format formats the message.
+// If source is enabled, this emits valid Go source.
+// If multi is enabled, the output may span multiple lines.
+func (m Message) format(source, multi bool) string {
+ var ss []string
+ var prefix, nextPrefix string
+ for _, v := range m {
+ // Ensure certain tokens have preceding or succeeding newlines.
+ prefix, nextPrefix = nextPrefix, " "
+ if multi {
+ switch v := v.(type) {
+ case Tag: // only has preceding newline
+ prefix = "\n"
+ case Denormalized: // only has preceding newline
+ if _, ok := v.Value.(Tag); ok {
+ prefix = "\n"
+ }
+ case Message, Raw: // has preceding and succeeding newlines
+ prefix, nextPrefix = "\n", "\n"
+ }
+ }
+
+ s := formatToken(v, source, multi)
+ ss = append(ss, prefix+s+",")
+ }
+
+ var s string
+ if len(ss) > 0 {
+ s = strings.TrimSpace(strings.Join(ss, ""))
+ if multi {
+ s = "\n\t" + strings.Join(strings.Split(s, "\n"), "\n\t") + "\n"
+ } else {
+ s = strings.TrimSuffix(s, ",")
+ }
+ }
+ s = fmt.Sprintf("%T{%s}", m, s)
+ if !source {
+ s = trimPackage(s)
+ }
+ return s
+}
+
+// formatToken formats a single token.
+func formatToken(t Token, source, multi bool) (s string) {
+ switch v := t.(type) {
+ case Message:
+ s = v.format(source, multi)
+ case LengthPrefix:
+ s = formatPacked(v, source, multi)
+ if s == "" {
+ ms := Message(v).format(source, multi)
+ s = fmt.Sprintf("%T(%s)", v, ms)
+ }
+ case Tag:
+ s = fmt.Sprintf("%T{%d, %s}", v, v.Number, formatType(v.Type, source))
+ case Bool, Varint, Svarint, Uvarint, Int32, Uint32, Float32, Int64, Uint64, Float64:
+ if source {
+ // Print floats in a way that preserves exact precision.
+ if f, _ := v.(Float32); math.IsNaN(float64(f)) || math.IsInf(float64(f), 0) {
+ switch {
+ case f > 0:
+ s = fmt.Sprintf("%T(math.Inf(+1))", v)
+ case f < 0:
+ s = fmt.Sprintf("%T(math.Inf(-1))", v)
+ case math.Float32bits(float32(math.NaN())) == math.Float32bits(float32(f)):
+ s = fmt.Sprintf("%T(math.NaN())", v)
+ default:
+ s = fmt.Sprintf("%T(math.Float32frombits(0x%08x))", v, math.Float32bits(float32(f)))
+ }
+ break
+ }
+ if f, _ := v.(Float64); math.IsNaN(float64(f)) || math.IsInf(float64(f), 0) {
+ switch {
+ case f > 0:
+ s = fmt.Sprintf("%T(math.Inf(+1))", v)
+ case f < 0:
+ s = fmt.Sprintf("%T(math.Inf(-1))", v)
+ case math.Float64bits(float64(math.NaN())) == math.Float64bits(float64(f)):
+ s = fmt.Sprintf("%T(math.NaN())", v)
+ default:
+ s = fmt.Sprintf("%T(math.Float64frombits(0x%08x))", v, math.Float64bits(float64(f)))
+ }
+ break
+ }
+ }
+ s = fmt.Sprintf("%T(%v)", v, v)
+ case String, Bytes, Raw:
+ s = fmt.Sprintf("%s", v)
+ s = fmt.Sprintf("%T(%s)", v, formatString(s))
+ case Denormalized:
+ s = fmt.Sprintf("%T{+%d, %v}", v, v.Count, formatToken(v.Value, source, multi))
+ default:
+ panic(fmt.Sprintf("unknown type: %T", v))
+ }
+ if !source {
+ s = trimPackage(s)
+ }
+ return s
+}
+
+// formatPacked returns a non-empty string if LengthPrefix looks like a packed
+// repeated field of primitives.
+func formatPacked(v LengthPrefix, source, multi bool) string {
+ var ss []string
+ for _, v := range v {
+ switch v.(type) {
+ case Bool, Varint, Svarint, Uvarint, Int32, Uint32, Float32, Int64, Uint64, Float64, Denormalized, Raw:
+ if v, ok := v.(Denormalized); ok {
+ switch v.Value.(type) {
+ case Bool, Varint, Svarint, Uvarint:
+ default:
+ return ""
+ }
+ }
+ ss = append(ss, formatToken(v, source, multi))
+ default:
+ return ""
+ }
+ }
+ s := fmt.Sprintf("%T{%s}", v, strings.Join(ss, ", "))
+ if !source {
+ s = trimPackage(s)
+ }
+ return s
+}
+
+// formatType returns the name for Type.
+func formatType(t Type, source bool) (s string) {
+ switch t {
+ case VarintType:
+ s = pkg + ".VarintType"
+ case Fixed32Type:
+ s = pkg + ".Fixed32Type"
+ case Fixed64Type:
+ s = pkg + ".Fixed64Type"
+ case BytesType:
+ s = pkg + ".BytesType"
+ case StartGroupType:
+ s = pkg + ".StartGroupType"
+ case EndGroupType:
+ s = pkg + ".EndGroupType"
+ default:
+ s = fmt.Sprintf("Type(%d)", t)
+ }
+ if !source {
+ s = strings.TrimSuffix(trimPackage(s), "Type")
+ }
+ return s
+}
+
+// formatString returns a quoted string for s.
+func formatString(s string) string {
+ // Use quoted string if it the same length as a raw string literal.
+ // Otherwise, attempt to use the raw string form.
+ qs := strconv.Quote(s)
+ if len(qs) == 1+len(s)+1 {
+ return qs
+ }
+
+ // Disallow newlines to ensure output is a single line.
+ // Disallow non-printable runes for readability purposes.
+ rawInvalid := func(r rune) bool {
+ return r == '`' || r == '\n' || r == utf8.RuneError || !unicode.IsPrint(r)
+ }
+ if strings.IndexFunc(s, rawInvalid) < 0 {
+ return "`" + s + "`"
+ }
+ return qs
+}
+
+var pkg = path.Base(reflect.TypeOf(Tag{}).PkgPath())
+
+func trimPackage(s string) string {
+ return strings.TrimPrefix(strings.TrimPrefix(s, pkg), ".")
+}
diff --git a/internal/encoding/pack/pack_test.go b/internal/encoding/pack/pack_test.go
new file mode 100644
index 0000000..36a6300
--- /dev/null
+++ b/internal/encoding/pack/pack_test.go
@@ -0,0 +1,352 @@
+// 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 pack
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "math"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ pref "google.golang.org/proto/reflect/protoreflect"
+ ptype "google.golang.org/proto/reflect/prototype"
+)
+
+var msgDesc = func() pref.MessageDescriptor {
+ mtyp, err := ptype.NewMessage(&ptype.StandaloneMessage{
+ Syntax: pref.Proto2,
+ FullName: "Message",
+ Fields: []ptype.Field{
+ {Name: "F1", Number: 1, Cardinality: pref.Repeated, Kind: pref.BoolKind, IsPacked: true},
+ {Name: "F2", Number: 2, Cardinality: pref.Repeated, Kind: pref.Int64Kind, IsPacked: true},
+ {Name: "F3", Number: 3, Cardinality: pref.Repeated, Kind: pref.Sint64Kind, IsPacked: true},
+ {Name: "F4", Number: 4, Cardinality: pref.Repeated, Kind: pref.Uint64Kind, IsPacked: true},
+ {Name: "F5", Number: 5, Cardinality: pref.Repeated, Kind: pref.Fixed32Kind, IsPacked: true},
+ {Name: "F6", Number: 6, Cardinality: pref.Repeated, Kind: pref.Sfixed32Kind, IsPacked: true},
+ {Name: "F7", Number: 7, Cardinality: pref.Repeated, Kind: pref.FloatKind, IsPacked: true},
+ {Name: "F8", Number: 8, Cardinality: pref.Repeated, Kind: pref.Fixed64Kind, IsPacked: true},
+ {Name: "F9", Number: 9, Cardinality: pref.Repeated, Kind: pref.Sfixed64Kind, IsPacked: true},
+ {Name: "F10", Number: 10, Cardinality: pref.Repeated, Kind: pref.DoubleKind, IsPacked: true},
+ {Name: "F11", Number: 11, Cardinality: pref.Optional, Kind: pref.StringKind},
+ {Name: "F12", Number: 12, Cardinality: pref.Optional, Kind: pref.BytesKind},
+ {Name: "F13", Number: 13, Cardinality: pref.Optional, Kind: pref.MessageKind, MessageType: ptype.PlaceholderMessage("Message")},
+ {Name: "F14", Number: 14, Cardinality: pref.Optional, Kind: pref.GroupKind, MessageType: ptype.PlaceholderMessage("Message")}},
+ })
+ if err != nil {
+ panic(err)
+ }
+ return mtyp
+}()
+
+// 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 TestPack(t *testing.T) {
+ tests := []struct {
+ raw []byte
+ msg Message
+
+ wantOutCompact string
+ wantOutMulti string
+ wantOutSource string
+ }{{
+ raw: dhex("080088808080800002088280808080000a09010002828080808000"),
+ msg: Message{
+ Tag{1, VarintType}, Bool(false),
+ Denormalized{5, Tag{1, VarintType}}, Uvarint(2),
+ Tag{1, VarintType}, Denormalized{5, Uvarint(2)},
+ Tag{1, BytesType}, LengthPrefix{Bool(true), Bool(false), Uvarint(2), Denormalized{5, Uvarint(2)}},
+ },
+ wantOutSource: `pack.Message{
+ pack.Tag{1, pack.VarintType}, pack.Bool(false),
+ pack.Denormalized{+5, pack.Tag{1, pack.VarintType}}, pack.Uvarint(2),
+ pack.Tag{1, pack.VarintType}, pack.Denormalized{+5, pack.Uvarint(2)},
+ pack.Tag{1, pack.BytesType}, pack.LengthPrefix{pack.Bool(true), pack.Bool(false), pack.Uvarint(2), pack.Denormalized{+5, pack.Uvarint(2)}},
+}`,
+ }, {
+ raw: dhex("100010828080808000121980808080808080808001ffffffffffffffff7f828080808000"),
+ msg: Message{
+ Tag{2, VarintType}, Varint(0),
+ Tag{2, VarintType}, Denormalized{5, Varint(2)},
+ Tag{2, BytesType}, LengthPrefix{Varint(math.MinInt64), Varint(math.MaxInt64), Denormalized{5, Varint(2)}},
+ },
+ wantOutCompact: `Message{Tag{2, Varint}, Varint(0), Tag{2, Varint}, Denormalized{+5, Varint(2)}, Tag{2, Bytes}, LengthPrefix{Varint(-9223372036854775808), Varint(9223372036854775807), Denormalized{+5, Varint(2)}}}`,
+ }, {
+ raw: dhex("1801188180808080001a1affffffffffffffffff01feffffffffffffffff01818080808000"),
+ msg: Message{
+ Tag{3, VarintType}, Svarint(-1),
+ Tag{3, VarintType}, Denormalized{5, Svarint(-1)},
+ Tag{3, BytesType}, LengthPrefix{Svarint(math.MinInt64), Svarint(math.MaxInt64), Denormalized{5, Svarint(-1)}},
+ },
+ wantOutMulti: `Message{
+ Tag{3, Varint}, Svarint(-1),
+ Tag{3, Varint}, Denormalized{+5, Svarint(-1)},
+ Tag{3, Bytes}, LengthPrefix{Svarint(-9223372036854775808), Svarint(9223372036854775807), Denormalized{+5, Svarint(-1)}},
+}`,
+ }, {
+ raw: dhex("200120818080808000221100ffffffffffffffffff01818080808000"),
+ msg: Message{
+ Tag{4, VarintType}, Uvarint(+1),
+ Tag{4, VarintType}, Denormalized{5, Uvarint(+1)},
+ Tag{4, BytesType}, LengthPrefix{Uvarint(0), Uvarint(math.MaxUint64), Denormalized{5, Uvarint(+1)}},
+ },
+ wantOutSource: `pack.Message{
+ pack.Tag{4, pack.VarintType}, pack.Uvarint(1),
+ pack.Tag{4, pack.VarintType}, pack.Denormalized{+5, pack.Uvarint(1)},
+ pack.Tag{4, pack.BytesType}, pack.LengthPrefix{pack.Uvarint(0), pack.Uvarint(18446744073709551615), pack.Denormalized{+5, pack.Uvarint(1)}},
+}`,
+ }, {
+ raw: dhex("2d010000002a0800000000ffffffff"),
+ msg: Message{
+ Tag{5, Fixed32Type}, Uint32(+1),
+ Tag{5, BytesType}, LengthPrefix{Uint32(0), Uint32(math.MaxUint32)},
+ },
+ wantOutCompact: `Message{Tag{5, Fixed32}, Uint32(1), Tag{5, Bytes}, LengthPrefix{Uint32(0), Uint32(4294967295)}}`,
+ }, {
+ raw: dhex("35ffffffff320800000080ffffff7f"),
+ msg: Message{
+ Tag{6, Fixed32Type}, Int32(-1),
+ Tag{6, BytesType}, LengthPrefix{Int32(math.MinInt32), Int32(math.MaxInt32)},
+ },
+ wantOutMulti: `Message{
+ Tag{6, Fixed32}, Int32(-1),
+ Tag{6, Bytes}, LengthPrefix{Int32(-2147483648), Int32(2147483647)},
+}`,
+ }, {
+ raw: dhex("3ddb0f49403a1401000000ffff7f7f0000c07f0000807f000080ff"),
+ msg: Message{
+ Tag{7, Fixed32Type}, Float32(math.Pi),
+ Tag{7, BytesType}, LengthPrefix{Float32(math.SmallestNonzeroFloat32), Float32(math.MaxFloat32), Float32(math.NaN()), Float32(math.Inf(+1)), Float32(math.Inf(-1))},
+ },
+ wantOutSource: `pack.Message{
+ pack.Tag{7, pack.Fixed32Type}, pack.Float32(3.1415927),
+ pack.Tag{7, pack.BytesType}, pack.LengthPrefix{pack.Float32(1e-45), pack.Float32(3.4028235e+38), pack.Float32(math.NaN()), pack.Float32(math.Inf(+1)), pack.Float32(math.Inf(-1))},
+}`,
+ }, {
+ raw: dhex("41010000000000000042100000000000000000ffffffffffffffff"),
+ msg: Message{
+ Tag{8, Fixed64Type}, Uint64(+1),
+ Tag{8, BytesType}, LengthPrefix{Uint64(0), Uint64(math.MaxUint64)},
+ },
+ wantOutCompact: `Message{Tag{8, Fixed64}, Uint64(1), Tag{8, Bytes}, LengthPrefix{Uint64(0), Uint64(18446744073709551615)}}`,
+ }, {
+ raw: dhex("49ffffffffffffffff4a100000000000000080ffffffffffffff7f"),
+ msg: Message{
+ Tag{9, Fixed64Type}, Int64(-1),
+ Tag{9, BytesType}, LengthPrefix{Int64(math.MinInt64), Int64(math.MaxInt64)},
+ },
+ wantOutMulti: `Message{
+ Tag{9, Fixed64}, Int64(-1),
+ Tag{9, Bytes}, LengthPrefix{Int64(-9223372036854775808), Int64(9223372036854775807)},
+}`,
+ }, {
+ raw: dhex("51182d4454fb21094052280100000000000000ffffffffffffef7f010000000000f87f000000000000f07f000000000000f0ff"),
+ msg: Message{
+ Tag{10, Fixed64Type}, Float64(math.Pi),
+ Tag{10, BytesType}, LengthPrefix{Float64(math.SmallestNonzeroFloat64), Float64(math.MaxFloat64), Float64(math.NaN()), Float64(math.Inf(+1)), Float64(math.Inf(-1))},
+ },
+ wantOutMulti: `Message{
+ Tag{10, Fixed64}, Float64(3.141592653589793),
+ Tag{10, Bytes}, LengthPrefix{Float64(5e-324), Float64(1.7976931348623157e+308), Float64(NaN), Float64(+Inf), Float64(-Inf)},
+}`,
+ }, {
+ raw: dhex("5a06737472696e675a868080808000737472696e67"),
+ msg: Message{
+ Tag{11, BytesType}, String("string"),
+ Tag{11, BytesType}, Denormalized{+5, String("string")},
+ },
+ wantOutCompact: `Message{Tag{11, Bytes}, String("string"), Tag{11, Bytes}, Denormalized{+5, String("string")}}`,
+ }, {
+ raw: dhex("62056279746573628580808080006279746573"),
+ msg: Message{
+ Tag{12, BytesType}, Bytes("bytes"),
+ Tag{12, BytesType}, Denormalized{+5, Bytes("bytes")},
+ },
+ wantOutMulti: `Message{
+ Tag{12, Bytes}, Bytes("bytes"),
+ Tag{12, Bytes}, Denormalized{+5, Bytes("bytes")},
+}`,
+ }, {
+ raw: dhex("6a28a006ffffffffffffffffff01a506ffffffffa106ffffffffffffffffa206056279746573a306a406"),
+ msg: Message{
+ Tag{13, BytesType}, LengthPrefix(Message{
+ Tag{100, VarintType}, Uvarint(math.MaxUint64),
+ Tag{100, Fixed32Type}, Uint32(math.MaxUint32),
+ Tag{100, Fixed64Type}, Uint64(math.MaxUint64),
+ Tag{100, BytesType}, Bytes("bytes"),
+ Tag{100, StartGroupType}, Tag{100, EndGroupType},
+ }),
+ },
+ wantOutSource: `pack.Message{
+ pack.Tag{13, pack.BytesType}, pack.LengthPrefix(pack.Message{
+ pack.Tag{100, pack.VarintType}, pack.Uvarint(18446744073709551615),
+ pack.Tag{100, pack.Fixed32Type}, pack.Uint32(4294967295),
+ pack.Tag{100, pack.Fixed64Type}, pack.Uint64(18446744073709551615),
+ pack.Tag{100, pack.BytesType}, pack.Bytes("bytes"),
+ pack.Tag{100, pack.StartGroupType},
+ pack.Tag{100, pack.EndGroupType},
+ }),
+}`,
+ }, {
+ raw: dhex("6aa88080808000a006ffffffffffffffffff01a506ffffffffa106ffffffffffffffffa206056279746573a306a406"),
+ msg: Message{
+ Tag{13, BytesType}, Denormalized{5, LengthPrefix(Message{
+ Tag{100, VarintType}, Uvarint(math.MaxUint64),
+ Tag{100, Fixed32Type}, Uint32(math.MaxUint32),
+ Tag{100, Fixed64Type}, Uint64(math.MaxUint64),
+ Tag{100, BytesType}, Bytes("bytes"),
+ Tag{100, StartGroupType}, Tag{100, EndGroupType},
+ })},
+ },
+ wantOutCompact: `Message{Tag{13, Bytes}, Denormalized{+5, LengthPrefix(Message{Tag{100, Varint}, Uvarint(18446744073709551615), Tag{100, Fixed32}, Uint32(4294967295), Tag{100, Fixed64}, Uint64(18446744073709551615), Tag{100, Bytes}, Bytes("bytes"), Tag{100, StartGroup}, Tag{100, EndGroup}})}}`,
+ }, {
+ raw: dhex("73a006ffffffffffffffffff01a506ffffffffa106ffffffffffffffffa206056279746573a306a40674"),
+ msg: Message{
+ Tag{14, StartGroupType}, Message{
+ Tag{100, VarintType}, Uvarint(math.MaxUint64),
+ Tag{100, Fixed32Type}, Uint32(math.MaxUint32),
+ Tag{100, Fixed64Type}, Uint64(math.MaxUint64),
+ Tag{100, BytesType}, Bytes("bytes"),
+ Tag{100, StartGroupType}, Tag{100, EndGroupType},
+ },
+ Tag{14, EndGroupType},
+ },
+ wantOutMulti: `Message{
+ Tag{14, StartGroup},
+ Message{
+ Tag{100, Varint}, Uvarint(18446744073709551615),
+ Tag{100, Fixed32}, Uint32(4294967295),
+ Tag{100, Fixed64}, Uint64(18446744073709551615),
+ Tag{100, Bytes}, Bytes("bytes"),
+ Tag{100, StartGroup},
+ Tag{100, EndGroup},
+ },
+ Tag{14, EndGroup},
+}`,
+ }, {
+ raw: dhex("d0faa972cd02a5f09051c2d8aa0d6a26a89c311eddef024b423c0f6f47b64227a1600db56e3f73d4113096c9a88e2b99f2d847516853d76a1a6e9811c85a2ab3"),
+ msg: Message{
+ Tag{29970346, VarintType}, Uvarint(333),
+ Tag{21268228, Fixed32Type}, Uint32(229300418),
+ Tag{13, BytesType}, LengthPrefix(Message{
+ Tag{100805, VarintType}, Uvarint(30),
+ Tag{5883, Fixed32Type}, Uint32(255607371),
+ Tag{13, Type(7)},
+ Raw("G\xb6B'\xa1`\r\xb5n?s\xd4\x110\x96ɨ\x8e+\x99\xf2\xd8GQhS"),
+ }),
+ Tag{1706, Type(7)},
+ Raw("\x1an\x98\x11\xc8Z*\xb3"),
+ },
+ }, {
+ raw: dhex("3d08d0e57f"),
+ msg: Message{
+ Tag{7, Fixed32Type}, Float32(math.Float32frombits(
+ // TODO: Remove workaround for compiler bug (see https://golang.org/issues/27193).
+ func() uint32 { return 0x7fe5d008 }(),
+ )),
+ },
+ wantOutSource: `pack.Message{
+ pack.Tag{7, pack.Fixed32Type}, pack.Float32(math.Float32frombits(0x7fe5d008)),
+}`,
+ }, {
+ raw: dhex("51a8d65110771bf97f"),
+ msg: Message{
+ Tag{10, Fixed64Type}, Float64(math.Float64frombits(0x7ff91b771051d6a8)),
+ },
+ wantOutSource: `pack.Message{
+ pack.Tag{10, pack.Fixed64Type}, pack.Float64(math.Float64frombits(0x7ff91b771051d6a8)),
+}`,
+ }, {
+ raw: dhex("ab2c14481ab3e9a76d937fb4dd5e6c616ef311f62b7fe888785fca5609ffe81c1064e50dd7a9edb408d317e2891c0d54c719446938d41ab0ccf8e61dc28b0ebb"),
+ msg: Message{
+ Tag{709, StartGroupType},
+ Tag{2, EndGroupType},
+ Tag{9, VarintType}, Uvarint(26),
+ Tag{28655254, StartGroupType},
+ Message{
+ Tag{2034, StartGroupType},
+ Tag{194006, EndGroupType},
+ },
+ Tag{13, EndGroupType},
+ Tag{12, Fixed64Type}, Uint64(9865274810543764334),
+ Tag{15, VarintType}, Uvarint(95),
+ Tag{1385, BytesType}, Bytes("\xff\xe8\x1c\x10d\xe5\rש"),
+ Tag{17229, Fixed32Type}, Uint32(2313295827),
+ Tag{3, EndGroupType},
+ Tag{1, Fixed32Type}, Uint32(1142540116),
+ Tag{13, Fixed64Type}, Uint64(2154683029754926136),
+ Tag{28856, BytesType},
+ Raw("\xbb"),
+ },
+ }, {
+ raw: dhex("29baa4ac1c1e0a20183393bac434b8d3559337ec940050038770eaa9937f98e4"),
+ msg: Message{
+ Tag{5, Fixed64Type}, Uint64(1738400580611384506),
+ Tag{6, StartGroupType},
+ Message{
+ Tag{13771682, StartGroupType},
+ Message{
+ Tag{175415, VarintType}, Uvarint(7059),
+ },
+ Denormalized{+1, Tag{333, EndGroupType}},
+ Tag{10, VarintType}, Uvarint(3),
+ Tag{1792, Type(7)},
+ Raw("꩓\u007f\x98\xe4"),
+ },
+ },
+ }}
+
+ equateFloatBits := cmp.Options{
+ cmp.Comparer(func(x, y Float32) bool {
+ return math.Float32bits(float32(x)) == math.Float32bits(float32(y))
+ }),
+ cmp.Comparer(func(x, y Float64) bool {
+ return math.Float64bits(float64(x)) == math.Float64bits(float64(y))
+ }),
+ }
+ for _, tt := range tests {
+ t.Run("", func(t *testing.T) {
+ var msg Message
+ raw := tt.msg.Marshal()
+ msg.UnmarshalDescriptor(tt.raw, msgDesc)
+
+ if !bytes.Equal(raw, tt.raw) {
+ t.Errorf("Marshal() mismatch:\ngot %x\nwant %x", raw, tt.raw)
+ }
+ if !cmp.Equal(msg, tt.msg, equateFloatBits) {
+ t.Errorf("Unmarshal() mismatch:\ngot %+v\nwant %+v", msg, tt.msg)
+ }
+ if got, want := tt.msg.Size(), len(tt.raw); got != want {
+ t.Errorf("Size() = %v, want %v", got, want)
+ }
+ if tt.wantOutCompact != "" {
+ gotOut := fmt.Sprintf("%v", tt.msg)
+ if string(gotOut) != tt.wantOutCompact {
+ t.Errorf("fmt.Sprintf(%q, msg):\ngot: %s\nwant: %s", "%v", gotOut, tt.wantOutCompact)
+ }
+ }
+ if tt.wantOutMulti != "" {
+ gotOut := fmt.Sprintf("%+v", tt.msg)
+ if string(gotOut) != tt.wantOutMulti {
+ t.Errorf("fmt.Sprintf(%q, msg):\ngot: %s\nwant: %s", "%+v", gotOut, tt.wantOutMulti)
+ }
+ }
+ if tt.wantOutSource != "" {
+ gotOut := fmt.Sprintf("%#v", tt.msg)
+ if string(gotOut) != tt.wantOutSource {
+ t.Errorf("fmt.Sprintf(%q, msg):\ngot: %s\nwant: %s", "%#v", gotOut, tt.wantOutSource)
+ }
+ }
+ })
+ }
+}