Computing History · Standards and Consortia
Why Compatible Standards Still Fail to Interoperate
The layers above the encoding where two conforming products diverge: version negotiation, timeouts, error handling and the assumptions each side makes about the other.
Two products can each pass a conformance suite for the same specification and still fail to exchange a single message. This is not a rare pathology. It is the ordinary outcome when a specification defines encoding and leaves behaviour to the implementer, and most specifications do exactly that, because behaviour is where the hard disagreements are.
The failure is structural rather than accidental, and the structure is consistent enough to enumerate. What follows is the set of places where conforming implementations diverge, ordered roughly by how often they matter.
Encoding is the part that gets specified
Byte layouts and field semantics are the easiest things to standardize, because they are checkable and because a committee can agree on them without agreeing on anything else. A specification that defines a message header down to the bit level, and says nothing about the order in which messages are exchanged, has standardized the one part that rarely fails.
The differential exercise between independent implementations, described in the account of reference implementations, usually finds encoding defects only at the margins: an unused bit that one side checks and the other does not, a length that excludes or includes a checksum. These are cheap to find when both implementations exist and expensive to find when they do not, which is why interoperability events concentrate on them.
The larger failures occur where the specification did not speak.
Version negotiation and the assumption of failure
When two products implement different revisions of the same specification, the exchange depends on what each assumes about the other’s support for newer fields.
The dangerous pattern is silent extension: a later revision adds a field, and senders assume that receivers on the earlier revision will ignore it, as the base specification requires. That assumption holds only if the field is genuinely ignorable — that is, if its absence changes no interpretation. A field that shifts the meaning of the remaining bytes is not ignorable, and a receiver that ignores it parses the rest incorrectly. Whether a field is ignorable is a property of the design, not of the specification’s instruction to ignore it. Specifications that add fields mid-structure, rather than appending them, break this property and break interoperability for every implementation that upgrades before its peers.
The second pattern is negotiation by failure: trying the newest message first and falling back when the peer rejects it. This works, costs a round trip, and depends entirely on the rejection being timely and precise. A peer that accepts the message and silently mishandles the field produces a failure that the fallback never triggers, and the symptom appears later and elsewhere.
A third pattern is the version field that means several things. A version number in a protocol can denote the revision of the format, the capability set of the sender, or the dialect the sender intends. Implementations that read one meaning where the sender wrote another interoperate until the first incompatible addition, and then the version check fails in a way that looks like a peer defect.
The requirement layering in RFC 1122 and its transport-layer companion is the classic remedy: a host must implement a defined set of behaviours, and the must/should distinction is applied to behaviour rather than only to format. Specifications written that way leave less to be guessed.
Timers and the cost of independent decisions
Nothing in most specifications determines how long a peer waits, how often it retries, or how many retries precede giving up. Each implementation chooses, and the choices interact.
Consider one side with a retransmission interval shorter than the other side’s processing time. The sender retransmits a request whose reply is in flight, the receiver processes both, and the protocol now relies on idempotence to avoid a duplicate effect. Whether the operation is idempotent may be true at one layer and false at another: a query is idempotent, and a request that triggers a side effect is not, even when both carry the same bytes at the transport.
Congestion control is the same problem at a larger scale. Two implementations that each behave reasonably in isolation can interact to produce oscillation when one backs off on a loss signal the other does not generate. The mechanism that makes this governable is a shared response to a shared signal — a defined reaction to loss and to delay — without which each implementation’s sane behaviour composes into an unstable aggregate. Standards that define the response to congestion are doing groundwork no conformance suite for message format can substitute for.
Timeout defaults are also the most commonly deployed undocumented behaviour. Two products that pass the same suite will differ on a five-second timeout by orders of magnitude, and the difference is invisible until a peer is slow.
Error handling: strictness, tolerance and the asymmetry of blame
The robustness principle asks senders to be conservative and receivers to be liberal. In deployment, the second half dominates, and its effect is that a receiver which rejects malformed input is judged defective.
The mechanism is a one-sided cost. A tolerant receiver accepts documents from every sender, so a strict receiver is the only product that fails on the corpus. The strict receiver has no way to demonstrate that it is right; it can only demonstrate that it is different. Over successive revisions, tolerance becomes part of the effective protocol, and a specification’s later revision typically concedes by describing the tolerant behaviour.
The danger is that tolerance applied to security-relevant parsing is a vulnerability class. A receiver that accepts several encodings of the same value has to ensure that every filter and every consumer agrees on which one it saw. Where they do not, the disagreement is the defect regardless of whether any single parser followed the specification. The failure is not a conformance failure; it is a composition failure, and it is invisible to any suite that tests each parser separately.
HTTP is the mature case. RFC 9110 collects semantics that had accreted over decades of implementation-defined behaviour — caching, conditional requests, redirect handling, framing — and writes down what implementations in aggregate had already settled. A specification that documents precedent is doing useful work; the questions it answers were previously answered by whichever client happened to be first on the wire.
State machines and the messages nobody sends
A specification’s state machine is usually written for the case where messages arrive in the expected order. Implementations differ on the cases that follow.
A message arriving in an unexpected state: some implementations ignore it, some reject and close, and some attempt to resynchronize. Each is a defensible reading of a specification that does not address the case, and the three produce different observable behaviour for the same peer input.
Simultaneous initiation: two peers that both open at once, each having decided the other initiated. Whether there are two connections or one, and which survives, is decided by a tiebreak rule that specifications sometimes state and implementations sometimes invert.
Half-closed states: one side has finished sending but is still reading. Behaviour here is where implementations that treat closure as a symmetric event diverge from those that treat it as a per-direction event.
For each of these, the divergent behaviour is reachable only by a peer that does something unusual. A normal peer never encounters the ambiguity. The failure therefore shows up when one side upgrades, or when a test tool probes the edge, not when two well-behaved products first connect.
What follows for an implementer
The usable conclusion is that conformance and interoperability are separate targets that require separate evidence. The evidence for the first is a test report; the evidence for the second is a record of a session with a genuinely independent implementation, including the cases where the specification was silent.
Where a specification has a profile, the profile is where the silent cases are narrowed, and choosing to implement the profile is typically more useful than implementing the base faithfully. The mechanics of that narrowing are covered in the discussion of profiles and conformance classes. Where no profile exists, the only reliable procedure is to read the peer’s behaviour as the specification’s second half, and treat each divergence as a fact about the specification rather than a defect in the other product.
References
- RFC 1122: Requirements for Internet Hosts — Communication Layers — IETF
- RFC 9110: HTTP Semantics — IETF
- End-to-End Arguments in System Design — J. H. Saltzer, D. P. Reed, D. D. Clark, ACM TOCS, Vol. 2 No. 4, 1984