Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 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 | |
| 5 | // pbdump is a tool for decoding the wire format for protocol buffer messages. |
| 6 | package main |
| 7 | |
| 8 | import ( |
| 9 | "bytes" |
| 10 | "flag" |
| 11 | "fmt" |
| 12 | "io/ioutil" |
| 13 | "log" |
| 14 | "os" |
| 15 | "path/filepath" |
| 16 | "sort" |
| 17 | "strconv" |
| 18 | "strings" |
| 19 | |
Damien Neil | 204f1c0 | 2018-10-23 15:03:38 -0700 | [diff] [blame] | 20 | descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor" |
Joe Tsai | 01ab296 | 2018-09-21 17:44:00 -0700 | [diff] [blame] | 21 | "github.com/golang/protobuf/v2/internal/encoding/pack" |
| 22 | "github.com/golang/protobuf/v2/internal/encoding/wire" |
Joe Tsai | 009e067 | 2018-11-27 18:45:07 -0800 | [diff] [blame] | 23 | "github.com/golang/protobuf/v2/internal/scalar" |
Joe Tsai | 01ab296 | 2018-09-21 17:44:00 -0700 | [diff] [blame] | 24 | "github.com/golang/protobuf/v2/reflect/protoreflect" |
| 25 | "github.com/golang/protobuf/v2/reflect/prototype" |
Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 26 | ) |
| 27 | |
| 28 | func main() { |
| 29 | log.SetFlags(0) |
| 30 | log.SetOutput(os.Stderr) |
| 31 | |
| 32 | var fs fields |
Joe Tsai | 769b9e7 | 2018-09-06 06:00:40 -0700 | [diff] [blame] | 33 | var flagUsages []string |
| 34 | flagVar := func(value flag.Value, name, usage string) { |
| 35 | flagUsages = append(flagUsages, fmt.Sprintf(" -%-16v %v", name+" "+value.String(), usage)) |
| 36 | flag.Var(value, name, usage) |
| 37 | } |
| 38 | flagBool := func(name, usage string) *bool { |
| 39 | flagUsages = append(flagUsages, fmt.Sprintf(" -%-16v %v", name, usage)) |
| 40 | return flag.Bool(name, false, usage) |
| 41 | } |
| 42 | flagVar(fieldsFlag{&fs, protoreflect.BoolKind}, "bools", "List of bool fields") |
| 43 | flagVar(fieldsFlag{&fs, protoreflect.Int64Kind}, "ints", "List of int32 or int64 fields") |
| 44 | flagVar(fieldsFlag{&fs, protoreflect.Sint64Kind}, "sints", "List of sint32 or sint64 fields") |
| 45 | flagVar(fieldsFlag{&fs, protoreflect.Uint64Kind}, "uints", "List of enum, uint32, or uint64 fields") |
| 46 | flagVar(fieldsFlag{&fs, protoreflect.Fixed32Kind}, "uint32s", "List of fixed32 fields") |
| 47 | flagVar(fieldsFlag{&fs, protoreflect.Sfixed32Kind}, "int32s", "List of sfixed32 fields") |
| 48 | flagVar(fieldsFlag{&fs, protoreflect.FloatKind}, "float32s", "List of float fields") |
| 49 | flagVar(fieldsFlag{&fs, protoreflect.Fixed64Kind}, "uint64s", "List of fixed64 fields") |
| 50 | flagVar(fieldsFlag{&fs, protoreflect.Sfixed64Kind}, "int64s", "List of sfixed64 fields") |
| 51 | flagVar(fieldsFlag{&fs, protoreflect.DoubleKind}, "float64s", "List of double fields") |
| 52 | flagVar(fieldsFlag{&fs, protoreflect.StringKind}, "strings", "List of string fields") |
| 53 | flagVar(fieldsFlag{&fs, protoreflect.BytesKind}, "bytes", "List of bytes fields") |
| 54 | flagVar(fieldsFlag{&fs, protoreflect.MessageKind}, "messages", "List of message fields") |
| 55 | flagVar(fieldsFlag{&fs, protoreflect.GroupKind}, "groups", "List of group fields") |
| 56 | printDesc := flagBool("print_descriptor", "Print the message descriptor") |
| 57 | printSource := flagBool("print_source", "Print the output in valid Go syntax") |
Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 58 | flag.Usage = func() { |
Joe Tsai | 769b9e7 | 2018-09-06 06:00:40 -0700 | [diff] [blame] | 59 | fmt.Printf("Usage: %s [OPTIONS]... [INPUTS]...\n\n%s\n", filepath.Base(os.Args[0]), strings.Join(append([]string{ |
Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 60 | "Print structured representations of encoded protocol buffer messages.", |
| 61 | "Since the protobuf wire format is not fully self-describing, type information", |
| 62 | "about the proto message can be provided using flags (e.g., -messages).", |
| 63 | "Each field list is a comma-separated list of field identifiers,", |
| 64 | "where each field identifier is a dot-separated list of field numbers,", |
| 65 | "identifying each field relative to the root message.", |
| 66 | "", |
| 67 | "For example, \"-messages 1,3,3.1 -float32s 1.2 -bools 3.1.2\" represents:", |
| 68 | "", |
Joe Tsai | 769b9e7 | 2018-09-06 06:00:40 -0700 | [diff] [blame] | 69 | " message M {", |
| 70 | " optional M1 f1 = 1; // -messages 1", |
| 71 | " message M1 {", |
| 72 | " repeated float f2 = 2; // -float32s 1.2", |
| 73 | " }", |
| 74 | " optional M3 f3 = 3; // -messages 3", |
| 75 | " message M3 {", |
| 76 | " optional M1 f1 = 1; // -messages 3.1", |
| 77 | " message M1 {", |
| 78 | " repeated bool f2 = 2; // -bools 3.1.2", |
| 79 | " }", |
| 80 | " }", |
| 81 | " }", |
Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 82 | "", |
| 83 | "Arbitrarily complex message schemas can be represented using these flags.", |
| 84 | "Scalar field types are marked as repeated so that pbdump can decode", |
| 85 | "the packed representations of such field types.", |
| 86 | "", |
| 87 | "If no inputs are specified, the wire data is read in from stdin, otherwise", |
| 88 | "the contents of each specified input file is concatenated and", |
| 89 | "treated as one large message.", |
| 90 | "", |
| 91 | "Options:", |
Joe Tsai | 769b9e7 | 2018-09-06 06:00:40 -0700 | [diff] [blame] | 92 | }, flagUsages...), "\n")) |
Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 93 | } |
| 94 | flag.Parse() |
| 95 | |
| 96 | // Create message types. |
| 97 | var desc protoreflect.MessageDescriptor |
| 98 | if len(fs) > 0 { |
| 99 | var err error |
| 100 | desc, err = fs.Descriptor() |
| 101 | if err != nil { |
| 102 | log.Fatalf("Descriptor error: %v", err) |
| 103 | } |
| 104 | if *printDesc { |
Joe Tsai | 769b9e7 | 2018-09-06 06:00:40 -0700 | [diff] [blame] | 105 | fmt.Printf("%#v\n", desc) |
Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 106 | } |
| 107 | } |
| 108 | |
| 109 | // Read message input. |
| 110 | var buf []byte |
| 111 | if flag.NArg() == 0 { |
| 112 | b, err := ioutil.ReadAll(os.Stdin) |
| 113 | if err != nil { |
| 114 | log.Fatalf("ReadAll error: %v", err) |
| 115 | } |
| 116 | buf = b |
| 117 | } |
| 118 | for _, f := range flag.Args() { |
| 119 | b, err := ioutil.ReadFile(f) |
| 120 | if err != nil { |
| 121 | log.Fatalf("ReadFile error: %v", err) |
| 122 | } |
| 123 | buf = append(buf, b...) |
| 124 | } |
| 125 | |
| 126 | // Parse and print message structure. |
| 127 | defer log.Printf("fatal input: %q", buf) // debug printout if panic occurs |
| 128 | var m pack.Message |
| 129 | m.UnmarshalDescriptor(buf, desc) |
| 130 | if *printSource { |
Joe Tsai | 769b9e7 | 2018-09-06 06:00:40 -0700 | [diff] [blame] | 131 | fmt.Printf("%#v\n", m) |
Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 132 | } else { |
Joe Tsai | 769b9e7 | 2018-09-06 06:00:40 -0700 | [diff] [blame] | 133 | fmt.Printf("%+v\n", m) |
Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 134 | } |
| 135 | if !bytes.Equal(buf, m.Marshal()) || len(buf) != m.Size() { |
| 136 | log.Fatalf("roundtrip mismatch:\n\tgot: %d %x\n\twant: %d %x", m.Size(), m, len(buf), buf) |
| 137 | } |
| 138 | os.Exit(0) // exit cleanly, avoid debug printout |
| 139 | } |
| 140 | |
| 141 | // fields is a tree of fields, keyed by a field number. |
| 142 | // Fields representing messages or groups have sub-fields. |
| 143 | type fields map[wire.Number]*field |
| 144 | type field struct { |
| 145 | kind protoreflect.Kind |
| 146 | sub fields // only for MessageKind or GroupKind |
| 147 | } |
| 148 | |
| 149 | // Set parses s as a comma-separated list (see the help above for the format) |
| 150 | // and treats each field identifier as the specified kind. |
| 151 | func (fs *fields) Set(s string, k protoreflect.Kind) error { |
| 152 | if *fs == nil { |
| 153 | *fs = make(fields) |
| 154 | } |
| 155 | for _, s := range strings.Split(s, ",") { |
| 156 | if err := fs.set("", strings.TrimSpace(s), k); err != nil { |
| 157 | return err |
| 158 | } |
| 159 | } |
| 160 | return nil |
| 161 | } |
| 162 | func (fs fields) set(prefix, s string, k protoreflect.Kind) error { |
| 163 | if s == "" { |
| 164 | return nil |
| 165 | } |
| 166 | |
| 167 | // Parse next field number. |
| 168 | i := strings.IndexByte(s, '.') |
| 169 | if i < 0 { |
| 170 | i = len(s) |
| 171 | } |
| 172 | prefix = strings.TrimPrefix(prefix+"."+s[:i], ".") |
| 173 | n, _ := strconv.ParseInt(s[:i], 10, 32) |
| 174 | num := wire.Number(n) |
| 175 | if num < wire.MinValidNumber || wire.MaxValidNumber < num { |
| 176 | return fmt.Errorf("invalid field: %v", prefix) |
| 177 | } |
| 178 | s = strings.TrimPrefix(s[i:], ".") |
| 179 | |
| 180 | // Handle the current field. |
| 181 | if fs[num] == nil { |
| 182 | fs[num] = &field{0, make(fields)} |
| 183 | } |
| 184 | if len(s) == 0 { |
| 185 | if fs[num].kind.IsValid() { |
| 186 | return fmt.Errorf("field %v already set as %v type", prefix, fs[num].kind) |
| 187 | } |
| 188 | fs[num].kind = k |
| 189 | } |
| 190 | if err := fs[num].sub.set(prefix, s, k); err != nil { |
| 191 | return err |
| 192 | } |
| 193 | |
| 194 | // Verify that only messages or groups can have sub-fields. |
| 195 | k2 := fs[num].kind |
| 196 | if k2 > 0 && k2 != protoreflect.MessageKind && k2 != protoreflect.GroupKind && len(fs[num].sub) > 0 { |
| 197 | return fmt.Errorf("field %v of %v type cannot have sub-fields", prefix, k2) |
| 198 | } |
| 199 | return nil |
| 200 | } |
| 201 | |
| 202 | // Descriptor returns the field tree as a message descriptor. |
| 203 | func (fs fields) Descriptor() (protoreflect.MessageDescriptor, error) { |
| 204 | ftyp, err := prototype.NewFile(&prototype.File{ |
| 205 | Syntax: protoreflect.Proto2, |
| 206 | Messages: []prototype.Message{fs.messageDescriptor("M")}, |
| 207 | }) |
| 208 | if err != nil { |
| 209 | return nil, err |
| 210 | } |
| 211 | return ftyp.Messages().Get(0), nil |
| 212 | } |
| 213 | func (fs fields) messageDescriptor(name protoreflect.FullName) prototype.Message { |
| 214 | m := prototype.Message{Name: name.Name()} |
| 215 | for _, n := range fs.sortedNums() { |
| 216 | f := prototype.Field{ |
| 217 | Name: protoreflect.Name(fmt.Sprintf("f%d", n)), |
| 218 | Number: n, |
| 219 | Cardinality: protoreflect.Optional, |
| 220 | Kind: fs[n].kind, |
| 221 | } |
| 222 | if !f.Kind.IsValid() { |
| 223 | f.Kind = protoreflect.MessageKind |
| 224 | } |
| 225 | switch f.Kind { |
| 226 | case protoreflect.BoolKind, protoreflect.EnumKind, |
| 227 | protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Uint32Kind, |
| 228 | protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Uint64Kind, |
| 229 | protoreflect.Sfixed32Kind, protoreflect.Fixed32Kind, protoreflect.FloatKind, |
| 230 | protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind, protoreflect.DoubleKind: |
| 231 | f.Cardinality = protoreflect.Repeated |
Joe Tsai | 009e067 | 2018-11-27 18:45:07 -0800 | [diff] [blame] | 232 | f.Options = &descriptorV1.FieldOptions{Packed: scalar.Bool(true)} |
Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 233 | case protoreflect.MessageKind, protoreflect.GroupKind: |
| 234 | s := name.Append(protoreflect.Name(fmt.Sprintf("M%d", n))) |
| 235 | f.MessageType = prototype.PlaceholderMessage(s) |
| 236 | m.Messages = append(m.Messages, fs[n].sub.messageDescriptor(s)) |
| 237 | } |
| 238 | m.Fields = append(m.Fields, f) |
| 239 | } |
| 240 | return m |
| 241 | } |
| 242 | |
| 243 | func (fs fields) sortedNums() (ns []wire.Number) { |
| 244 | for n := range fs { |
| 245 | ns = append(ns, n) |
| 246 | } |
| 247 | sort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] }) |
| 248 | return ns |
| 249 | } |
| 250 | |
Joe Tsai | 769b9e7 | 2018-09-06 06:00:40 -0700 | [diff] [blame] | 251 | // fieldsFlag is an implementation of flag.Value that is keyed a specific kind. |
| 252 | type fieldsFlag struct { |
| 253 | f *fields |
| 254 | k protoreflect.Kind |
| 255 | } |
Joe Tsai | b4e370e | 2018-08-15 14:59:51 -0700 | [diff] [blame] | 256 | |
Joe Tsai | 769b9e7 | 2018-09-06 06:00:40 -0700 | [diff] [blame] | 257 | func (fs fieldsFlag) String() string { return "FIELDS" } |
| 258 | func (fs fieldsFlag) Set(s string) error { return fs.f.Set(s, fs.k) } |