ModernCalcs

Protobuf Decoder

Decode raw Protocol Buffers bytes without a .proto schema — inspect field numbers, wire types, and values, including nested messages.

#1Length-delimited"hello"
#2Varint42
#3Length-delimitedMessage (2 fields)
#1Varint1
#2Varint2

Protobuf Decoder: Inspect Raw Protocol Buffers Bytes Without a Schema

Protocol Buffers encode data extremely compactly, but that compactness means the raw bytes are unreadable without the original .proto schema — or a decoder that understands the wire format itself. This tool walks the byte stream field by field, reporting each field's number, wire type, and value, and recursively decoding nested messages.

Formula
tag = (field_number << 3) | wire_type

Wire types: 0 = Varint, 1 = 64-bit, 2 = Length-delimited, 5 = 32-bit.

Why Protobuf Doesn't Need Field Names on the Wire

Unlike JSON, Protocol Buffers encode only a field's number and its raw value — never its name. The .proto schema maps numbers back to names at compile time. This makes messages smaller and faster to parse, but means any schema-less decoder (including this one) can only show you 'field #3', not what that field is called.

Resolving the Length-Delimited Ambiguity

Wire type 2 (length-delimited) is used for strings, raw bytes, and nested messages alike — the wire format doesn't distinguish between them. This decoder guesses by first attempting to parse the bytes as a valid nested message; if that fails, it tries UTF-8 text; if that also fails, it shows the raw byte count. This mirrors exactly how official tools like protoc --decode_raw behave, ambiguity included.

Varints: Protobuf's Space-Saving Integer Encoding

Wire type 0 uses a variable-length encoding where small numbers take fewer bytes — values under 128 fit in a single byte. Each byte uses its top bit to signal 'more bytes follow', which is why a naive byte-by-byte reader can't just look at fixed positions; it has to walk the continuation bits.

Practical Examples

Decoding a Simple Message

A message with a string, an integer, and a nested message.

  • 1.Hex: 0a 05 68 65 6c 6c 6f 10 2a 1a 04 08 01 10 02
  • 2.Field #1 (string): "hello"
  • 3.Field #2 (varint): 42
  • 4.Field #3 (nested message): { #1: 1, #2: 2 }

Reading a gRPC-Captured Payload

Pasting bytes copied from a network proxy tool.

  • 1.Copy base64 payload from proxy log
  • 2.Switch format to Base64
  • 3.Paste and inspect the decoded field tree

The Four Wire Types

  • 0 — Varint: int32, int64, uint32, uint64, bool, enum
  • 1 — 64-bit: fixed64, sfixed64, double
  • 2 — Length-delimited: string, bytes, embedded messages, packed repeated fields
  • 5 — 32-bit: fixed32, sfixed32, float

Good Use Cases

  • Reverse-engineering an undocumented protobuf API
  • Debugging a gRPC payload without the original .proto file
  • Verifying that a client is encoding fields correctly
  • Learning how the protobuf wire format actually works, byte by byte

Frequently Asked Questions

Do I need the .proto schema file to use this?

No. This decoder walks the raw wire format directly — reading field numbers and wire types — without any schema. That's exactly what tools like protoc --decode_raw do, and it's why field names aren't shown, only field numbers.

Why do I only see field numbers instead of field names?

Field names exist only in the .proto schema, not in the encoded bytes themselves. Protobuf's wire format is deliberately schema-less at the byte level for compactness — the field number is the only identifier present.

How does the decoder know a length-delimited field is a nested message vs. a string?

It first tries to parse the bytes as a valid nested protobuf message. If that parses cleanly, it shows the nested field tree. If not, it tries decoding the bytes as UTF-8 text. If neither works, it falls back to showing the raw byte count. This is a heuristic — occasionally a string can accidentally look like a valid nested message, the same ambiguity official schema-less protobuf tools have.

What are the four wire types?

0 (Varint) for integers and booleans, 1 (64-bit) for fixed64/double, 2 (Length-delimited) for strings, bytes, and nested messages, and 5 (32-bit) for fixed32/float.

Can I paste base64-encoded protobuf bytes?

Yes — switch the format toggle to Base64. This is useful since many APIs and logs represent binary protobuf payloads as base64 text.

Where would I actually use this?

Debugging gRPC payloads, inspecting binary data captured from a network proxy, or reverse-engineering an undocumented protobuf-based API where you don't have access to the original .proto definitions.