Skip to content

1.12.0 — proto3 presence model, and canonical encoding

Choose a tag to compare

@freitasjca freitasjca released this 06 Sep 14:32
· 110 commits to main since this release

1.12.0 — proto3 presence model, and canonical encoding

Wire format changed (read this first)

Default-valued scalars are no longer emitted. Messages get smaller and become
canonical proto3. A conforming decoder fills an absent field with its default,
so peers read exactly what they read before — the 54-case cross-language
interop suite still round-trips through google.protobuf unchanged.

One consequence worth knowing: deserialising into a reused target no longer
overwrites fields that are absent from the wire. Deserialize is protobuf's
MergeFromString, not ParseFromString, and always was — our non-canonical
output merely masked it whenever both ends were this library. Talking to
Python, Go or C++ has always behaved this way. Pass a fresh instance if you
want parse-from-scratch.

Conformance probe: CONFORMS 8 / DEVIATES 5 → 11 / 2. The remaining two are
structural rather than unfinished — TProtoMemberAttribute carries only a tag,
so zigzag and fixed-width cannot be requested.

New: proto3 optional

[TProtoHas(tag)] marks a read-only Boolean as a field's has-bit, making
"set to zero" distinguishable from "not set".

private
  Fb: Integer;  FhasB: Boolean;
  procedure SetB(const AValue: Integer);   // Fb := AValue; FhasB := True
published
  [TProtoMember(3)] property b:    Integer read Fb write SetB;
  [TProtoHas(3)]    property hasB: Boolean read FhasB;   // no writer

The has-bit must be read-only, and that is enforced. Deserialisation raises it
by writing the value through its setter; a writable bit would let a decoded
field arrive with its value set and its presence still False. protogen
generates the whole shape — value, setter, read-only bit, and Clear.

New: proto3 oneof

Members are has-bit fields whose setters clear their siblings, plus a computed
case discriminator. No codec change was required: a oneof has no wire framing
of its own — each member is an ordinary tagged field, and "at most one is set"
is a language-binding rule. Because deserialisation reaches the setters,
proto3's "last member on the wire wins" falls out for free.

Message members inside a oneof are refused: clearing one means freeing it,
and protogen emits no destructor.

Tests

Codec 97 → 98 · protogen parser 122 → 155 · emitter 32 → 46. Two new gates:

  • gRPC framing and reassembly under adversarial chunk boundaries — one
    byte stream delivered seven ways, since decoding per DATA frame works
    against a client that sends one message per frame and corrupts against
    every real one.
  • Generated code compiled and executed — optional and oneof output
    is put in front of a compiler and a live RTTI registry, not just compared
    as text. It caught generated Pascal that did not compile while fourteen
    text checks were green on that same output.

Green on FPC trunk 3.3.1 and Delphi 12 (dcc64 36.0).

Not shipped by Boss

tools/protogen/ sits outside mainsrc: "src/", so the code generator still
reaches consumers only by cloning the repository.

Compatibility

No change is required in horse-provider-nghttp2, and its >=1.10.0 floor
still resolves correctly: nothing outside the codec is involved, and the
previous encoding was wire-legal rather than wrong, so pinning an earlier
version remains defensible.