b2KIT

Protocol Buffer Decoder

Decode raw Protocol Buffer binary messages without a .proto schema using wire type analysis.

Tested tool guide Tested browser tools Checked August 16, 2026

What Protocol Buffer Decoder does, with a checked example

A Protocol Buffer message is a stream of tag-value pairs, and every tag carries its own field number and wire type, so the bytes can be read even when the .proto file is missing. This tool takes pasted hex (or raw bytes) and walks the stream: it resolves each varint tag, then parses the value that follows. What surprises people most is how much stays unknown: an integer could be signed, unsigned, a bool, or an enum, and a length-delimited chunk could be text, bytes, or another nested message. The output is a best-effort reading, not the schema.

Worked example

A concrete input and expected output from the current implementation.

Input

0a02686910960119000000000000f03f

Expected output

field 1 (wire 2, length-delimited): "hi"
field 2 (wire 0, varint): 150
field 3 (wire 1, 64-bit): 1.0 (double, hex 0x3FF0000000000000)

The tag bytes 0x0A, 0x10, and 0x19 decode to field 1 with wire type 2, field 2 with wire type 0, and field 3 with wire type 1. The varint 0x96 0x01 carries 22 + 128 = 150, and the final eight bytes are little-endian IEEE 754 for 1.0 (0x3FF0000000000000).

How the result is produced

1

Tag and wire type

Every field opens with a tag varint. The low 3 bits are the wire type: 0 means a varint value, 1 a fixed 64-bit value, 2 a length-delimited value, 3 and 4 deprecated group markers, 5 a fixed 32-bit value. The remaining bits are the field number. The decoder reads the tag, then skips exactly the value bytes that the wire type dictates, so it stays in step without the schema.

2

Value heuristics

Wire type 0 values are read as base-128 little-endian varints and shown unsigned, because int32, int64, uint64, bool, and enum all share that encoding and only the schema tells them apart. Wire types 1 and 5 render as double and float next to their raw hex. Length-delimited values are shown as UTF-8 text when that parses cleanly, otherwise as hex, with an optional recursive submessage reading that is speculative.

Good uses

  • Debugging gRPC or other protobuf-encoded traffic captured in logs or packet dumps when you do not have the service's .proto file.
  • Reverse-engineering an unfamiliar API: paste sample payloads and read off field numbers and value shapes to reconstruct the message layout by hand.
  • Verifying that a message your own code produced encodes what you expect, byte by byte, when a parser rejects it.

Limits and checks

  • A varint that prints as a huge unsigned number, like 18446744073709551615, is almost always a sign-extended negative int32 or int64, not a real 64-bit count.
  • Wire type 2 is ambiguous by design: the bytes could be a string, raw bytes, a packed repeated field, or a nested message, and whichever rendering the tool picks is a guess.
  • Any byte sequence decodes successfully: there is no checksum, so a truncated payload or an off-by-one offset still produces plausible-looking output that is silently wrong.

Common questions

Why does my -1 come back as 18446744073709551615?

Negative int32 and int64 values are sign-extended to 64 bits before varint encoding, and the varint itself carries no sign, so the wire bytes for -1 read as 2^64 - 1. Only the field's declared type in the schema turns that back into -1; the decoder can flag it as a likely negative, but cannot know for certain.

Can this tool recover field names or my .proto file?

No. Names live only in the .proto file and never reach the wire, so no decoder can recover them. What you get is field numbers, wire types, and value guesses, which is usually enough to reconstruct a working schema by hand, and then protoc can regenerate message classes from it.

References and verification

The example and behavioral notes were checked against the browser implementation. Standards and primary references below define the relevant format, formula, or platform behavior.

Related Tools