WireCodecConformanceSuite

The contract every wire codec fed by peer-controlled bytes must satisfy about the widths its own documentation fixes: a frame whose fixed-width field is short by one, or long by one, is rejected.

Subclass once per wire type, declare its fields, and point decode at the codec's entry point:

class NwHelloWireCodecTest : WireCodecConformanceSuite() {
override fun decode(frame: ByteArray): Any? = NwHello.decode(frame)
override fun rejectionMode(): WireRejectionMode = WireRejectionMode.Throwing
override fun exactWidthDeclaration(): ObligationDeclaration = ObligationDeclaration.Proven
override fun exactWidthFields(): List<ExactWidthField> =
listOf(ExactWidthField("nonce", NONCE_BYTES) { w -> helloBodyWithNonceWidth(w) })
// …
}

Why a suite and not five require lines

#1822 found the same defect at three sites — a nonce whose width is documented by a NONCE_BYTES constant and unenforced on decode — in three modules, with three different serializers. Two were copy-propagation; the third was an independent re-derivation by a different hand, which is what separates a recurring class from a duplicated mistake. Per-site requires fix the three instances and leave the class generative: the fourth fabric's author has nothing to inherit and re-derives the omission along with the format.

A lint rule cannot close it. "A documented width that isn't checked" relates prose to a comparison, and the syntactic-shape guards this repo does use (forbidBareSeamStateFlow, forbidBareLaunchIn) work precisely because their targets are call shapes. A TCK can, and it rides a habit contributors already have here — a new fabric subclasses a conformance suite.

Why rejection, never reshaping

Every field this suite covers is an identity or a MAC input, not a quantity. A quantity can be clamped into range; an identity cannot. Truncating or padding a wrong-width nonce to its declared width launders the proof of a malformed or forged frame into a valid-looking value — the forger simply receives whichever in-range value the reshaping picks. So the property is rejected, and a codec that silently reshapes fails it. (ObligationDeclaration.NotApplicable has no arm for "we reshape on purpose" because reshaping an identity is not a design choice this contract recognises; a codec that does it declares ContractDiffers and has to demonstrate it.)

Where the check belongs — the constructor, not the decoder (#1822 remedy 3)

This suite is the regression lock; it does not say where the check goes. It goes in the wire type's init { require(...) }. kotlinx-serialization invokes the constructor, so the invariant then holds on every decode path automatically, including one a future consumer adds and forgets to guard — whereas a decoder-side check sits one call site away from the type and covers only the paths somebody remembered. LogRecord, SpanRecord and MetricKey in :kuilt-otel are the exemplars; MeshHello, NwHello and TapAdmitMessage.Challenge are the fabric-side ones.

Two consequences, both of which have bitten:

  • require throws, so a WireRejectionMode.ReturningNull codec must catch it. A constructor throw is a rejection only under WireRejectionMode.Throwing. Left to escape from a decoder on a long-lived pump it ends the pump — #1819, where 16 bytes from any peer left a NearbySeam permanently deaf with no Torn to observe. The require still belongs on the type; the decoder is what turns it into null.

  • It can delete a test's detection while leaving the test green. A harness that builds its malformed frame by handing the local encoder a wrong-width value stops exercising the receiver the moment the constructor refuses: the sender throws, the frame never exists, and the assertion still passes. That is why ExactWidthField requires a width-unconstrained surrogate with a byte-identity receipt rather than the real encoder — the same reasoning, met one level earlier.

The knobs, and what each of them switches off

A fixture's configuration is a prescription too, and it drifts toward the setting where the property cannot fail. The three this suite has:

  • The declared width comes from the codec's own constant, never a literal. A harness that writes 16 rather than NONCE_BYTES keeps passing after the constant moves, testing a width the codec no longer has.

  • 0 is tested alongside w ± 1. The boundary is the strongest single case, but zero is the width that actually bit at two of the three sites — an empty nonce hex-encodes to the empty string, so two distinct peers derive one link identity, and it collapses a MAC input to HMAC(code, ""). It is only the same case as w - 1 when w is 1, and the suite dedupes.

  • everyExactWidthFieldIsAcceptedAtItsDeclaredWidth is a precondition, not a bonus. Without it, "rejects short and long" is satisfied by a codec that rejects everything — including one whose rig builds garbage at every width. It is the assertion that proves the rig fired.

What this suite cannot detect

  • Why a frame was refused. It sees the shape of a refusal (WireRejectionMode) but not its reason, so a rig whose mutation also breaks the parse is indistinguishable from one that isolates the width. ExactWidthField says how to avoid writing that rig; the per-site revert evidence is what confirms nobody did.

  • A field it was never told about. The declaration hooks are the reason an empty list cannot pass silently, but nothing forces a harness to declare its second field. Adding a fixed-width field to a wire type means adding it here, in the same PR, exactly as adding a module means adding its row to CLAUDE.md.

  • Value ranges. A chunkCount bound per-message and checked per-chunk (#1819) is a constraint on a field's value, not its width, and no arm of this suite reaches it. That is why ChunkCodecWireCodecTest declares NotConstructible on the exact-width obligation rather than dressing a value constraint up as a width one.

Constructors

Link copied to clipboard
constructor()

Functions

The precondition every other exact-width property rests on: the rig can build a frame this codec accepts.

A frame whose fixed-width field is empty is rejected.

A frame whose fixed-width field is one byte long is rejected.

A frame whose fixed-width field is one byte short is rejected.

The rig varies the width and nothing else: its frames grow strictly with the declared width, so a rig returning one constant frame, or unrelated frames, is caught.

A frame of exactly the header width is accepted, and so is one a byte longer.

A frame too short to hold the whole header is rejected, at every length below it.

The header rig returns frames of exactly the size it was asked for.

Link copied to clipboard

Whichever arm the harness declares for the exact-width obligation, the suite checks it.

Link copied to clipboard

Whichever arm the harness declares for the header obligation, the suite checks it.

Link copied to clipboard

A harness that declares nothing on either obligation is not a conformance test.