proto3 Messages and Wire Types

A .proto file declares messages whose fields each carry a field number. Money travels as integer cents (protobuf 59,573 has no decimal), and the timestamp uses the well-known type google.protobuf.Timestamp:

order.proto: the BookNest order in proto3
syntax = "proto3";
package booknest.v1;
import "google/protobuf/timestamp.proto";
enum Channel {
  CHANNEL_UNSPECIFIED = 0; CHANNEL_IOS = 1; CHANNEL_ANDROID = 2; CHANNEL_WEB = 3;
}
message Item { int32 book_id = 1; int32 qty = 2; int64 unit_price_cents = 3; }
message Order {
  int64 order_id = 1;
  int32 customer_id = 2;
  google.protobuf.Timestamp order_ts = 3;
  Channel channel = 4;
  string status = 5;
  string currency = 6;
  repeated Item items = 7;
  optional string coupon = 8;      // explicit presence: unset differs from ""
  int64 discount_cents = 9;
  int64 total_cents = 10;
}

to_pb.py (in demos/ch03/proto/) fills an Order from order 1's JSON line, and protoc --decode_raw parses the resulting bytes with no schema at all:

show_wire.sh: order 1 on the protobuf wireShell
protoc --python_out=. order.proto         # generates order_pb2.py
python encode_one.py                      # to_pb.py fills an Order from order 1's JSON line
xxd -g1 order1.bin
protoc --decode_raw < order1.bin          # no schema needed: numbers and wire types only
Output
order1.bin: 52 bytes (Avro body 42, JSON line 253)
00000000: 08 01 10 d6 13 1a 06 08 9e 8c d2 bb 06 20 01 2a  ............. .*
00000010: 09 64 65 6c 69 76 65 72 65 64 32 03 55 53 44 3a  .delivered2.USD:
00000020: 07 08 03 10 01 18 e0 12 3a 07 08 05 10 01 18 d4  ........:.......
00000030: 0c 50 b4 1f                                      .P..
1: 1
2: 2518
3 {
  1: 1735689758
}
...
10: 4020

Each field starts with a varint tag, (field_number << 3) | wire_type: 08 is field 1 as a varint, 1a field 3 and 3a field 7, both length-delimited. The wire types are VARINT (0) for integers, booleans and enums; I64 (1) for fixed64 and double; LEN (2) for strings, bytes, nested messages and packed repeated numbers; and I32 (5) for fixed32 and float (3 and 4 are proto2's deprecated groups). Numbers 1 to 15 fit a one-byte tag.

proto3 skips a singular field holding its default (zero, empty string, first enum value), so discount_cents and the unset coupon are absent and a reader cannot tell 0 from "not set". Marking a field optional, as with coupon, restores explicit presence and HasField(). Plain int64 spends 10 bytes on a negative number; sint64 uses zigzag, as Avro 129 does.