Skip to content

test(otel): decode the wire encoder's output with an independent decoder - #231

Merged
plusky merged 1 commit into
mainfrom
test/otlp-wire-differential-201
Sep 2, 2026
Merged

test(otel): decode the wire encoder's output with an independent decoder#231
plusky merged 1 commit into
mainfrom
test/otlp-wire-differential-201

Conversation

@plusky

@plusky plusky commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Closes #201.

otel.rs's wire module hand-rolls protobuf. A wrong length prefix does not
throw — the collector silently drops the record and the audit-adjacent
export goes dark. Everything else in the audit path fails loudly or not at all.

The gap is not where the issue says

The existing hand-written extractor already decodes the emitted bytes, and it
catches all three failure modes the issue names. What it misses:

  • Wire types. fields() normalizes a decoded varint to to_le_bytes()
    byte-identical to a fixed64 payload. A fixed64 emitted as a varint passes.
    A real collector rejects it.
  • Anything unexpected. only() takes the first match by field number and
    nothing asserts the buffer holds no more, so duplicated fields, undeclared
    fields and non-canonical varints are all invisible.

Seven-mutation matrix, reproduced independently by review:

mutation old new
length prefix len+1 caught caught
dropped continuation byte caught caught
field number 3 → 4 caught caught
fixed64 → varint missed caught
field emitted twice missed caught
undeclared field appended missed caught
non-canonical varint for zero missed caught

Shape

prost as a dev-dependency only — no protoc, no .proto, no build.rs.
The messages are hand-transcribed derives, which is what makes them an
independent oracle rather than generated code sharing the encoder's assumptions.
Review diffed every one against opentelemetry-proto-0.32.0: no mirrored
transcription error.

proptest rejected: the input space is narrow and varint widths enumerable, so
a targeted table is exhaustive where it matters and deterministic.

That choice earned itself — the byte-identity test passed under the
padded-zero mutation, because its fixtures contain no zero-valued varint. Only
the boundary tables caught it. They are not redundant with it.

Cost

Four crates in the dev graph (prost, prost-derive, itertools, either),
all already-allowed licences, no new duplicates, none reaching the shipped
binary
(cargo tree -e normal: zero hits). MSRV clear.

No encoder defect foundwire is correct and untouched.

DESIGN.md's rationale for hand-rolling said cargo deny "has nothing new to
judge, and Cargo.lock is unchanged by this feature". Both were falsified by
this change, so the clauses are rewritten rather than corrected by juxtaposition:
the runtime graph is unchanged; the lock and deny's scope are not.

Review: MERGE-SAFE.

The existing wire tests already decode the emitted bytes, but with a
hand-written extractor that shares its author's reading of the encoding
spec with the encoder it checks. Three blind spots follow: `only`/`all`
select a field by number and drop the wire type (a varint payload comes
back as the same eight little-endian bytes a fixed64 would); they only
look for the fields they expect, so a duplicated or undeclared one goes
unseen; and `read_varint` accepts non-canonical encodings `put_varint`
never emits. A wrong length prefix is not an error at the far end — the
collector drops the record silently — so the oracle has to be
independent.

prost as a dev-dependency, with the OTLP messages transcribed as
`prost::Message` derives in the test module: no .proto, no build script,
no protoc, four crates in the dev graph and none in the shipped binary.
Five tests: varints byte-for-byte against prost's own encoder over both
sides of every 7-bit boundary, a full semantic decode of a request,
byte-identity on decode-then-re-encode (the unknown/duplicate/
non-canonical check), length prefixes around 127/128 and 16383/16384,
and int attributes at the signed extremes.

Mutation-checked, seven mutations. Wrong length prefix, dropped
continuation byte and a swapped field number are caught by both old and
new tests. Four are caught only by the new ones: a fixed64 written as a
varint under the same field number, a duplicated severity_text, an
undeclared field 12 on LogRecord, and a padded two-byte varint for zero.

DESIGN.md's dependency-decision bullet claimed zero crates in the lock
file and nothing new for cargo deny; both are now false, so the claims
are narrowed to the shipped binary and the dev-graph cost is stated.

Targeted boundary cases rather than proptest: the encoder's input space
is narrow and the varint widths are enumerable, so the table is both
exhaustive where it matters and legible. It also earns its keep against
the byte-identity test — the padded-zero mutation slips past that one
on a fixture with no zero-valued varint, and only the boundary and
extremes tables catch it.
@plusky plusky added the ai-assisted Authored or substantially written with an AI coding agent label Sep 2, 2026
@plusky
plusky merged commit c3b6590 into main Sep 2, 2026
16 checks passed
@plusky
plusky deleted the test/otlp-wire-differential-201 branch September 2, 2026 07:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assisted Authored or substantially written with an AI coding agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Differential/property tests for the hand-written OTLP wire encoder — a bad length prefix fails silently at the collector

1 participant