pub enum ErrorCause {
Codec {
error: CodecError,
close: Option<u64>,
},
Endpoint,
StreamReset(u64),
Stopped(u64),
SessionClosed(u64),
StreamEnded,
Transport,
Facade,
PeerViolation {
rule: AboveCodecRule,
close: Option<u64>,
},
Unclassified,
}Expand description
Why a call through this facade stopped, kept as a value rather than as words.
§What flattening cost, and what it bought
The drafts each state their own ConnectionError, and this facade
exists so that a caller never has to branch on which. Rendering every draft
through Display bought exactly that — at the price of the variant, which
is the half a caller most often needs. Three questions could not be asked of
a sentence:
- Did the peer reset this stream, or finish it? A subgroup ends when its stream ends, so every reader’s last read fails; whether it failed because the peer abandoned the stream or because there was nothing more to send is the difference between two entirely different findings, and both arrived spelled as prose.
- Which rule stopped a decode? A decoder built for the negotiated draft
refusing a frame is sometimes this build failing to keep up and sometimes
this build doing what the draft requires.
Self::Codeccarries the error itself and the code the draft answers it with, so the two are separable without reading the message. - Was it the peer’s fault at all?
AnyConnectionError::is_local.
§The ten every draft shares, and the ones only some do
The first ten variants of ConnectionError are identical across all
drafts — Endpoint, Codec, Transport, VarInt,
NoControlStream, UnexpectedEnd, StreamFinished, InvalidAddress,
TlsConfig, DataStreamState — and those are classified here, once, rather
than fourteen times.
Six of the drafts add variants of their own, and those are read by
Connection::draft_specific_cause on each draft, beside the doc comment
quoting the sentence it enforces. They divide into this endpoint refusing to
write something (Self::Facade) and a peer breaking a rule the decoder
could not see (Self::PeerViolation) — two opposite findings that reached
a caller as prose and read exactly alike. See
crate::above_codec_rules.
§The same split, one layer in
ConnectionError::Endpoint wraps a fifteenth type per draft — that draft’s
EndpointError, twenty-six to forty-one variants holding the same two
findings under one name. EndpointError::fault divides it the same way and
by the same rule: a variant raised while reading what the peer sent is
the peer’s, and one raised while writing, or refusing to, is this
side’s. A handful are raised on both paths and the variant cannot say which;
those stay on this side, where they already were.
Variants§
Codec
A decoder built for the negotiated draft refused what arrived.
close is the session error code that draft’s own text requires be
sent for this error, where it names one — which is the whole of what
separates a relay’s defect from this build’s shortfall. A decoder
stopping is not by itself a finding about the peer; a decoder stopping on
a rule the draft answers with “MUST close the session” is.
None covers both a rule the draft states without a close and an error
the draft says nothing about, and deliberately does not distinguish
them: neither is grounds to name a relay.
Fields
error: CodecErrorThe decoder’s own error, unflattened.
Endpoint
A message that did not fit the session’s state, and the fault is not the peer’s.
Two things reach here, and EndpointError::fault tells them apart on
each draft. Most are this endpoint refusing on the way out — a response
offered for a request the draft says to refuse, an alias it was asked to
give to a second track, a session that has closed — where nothing
reached the wire. The rest are variants raised on both a receive path
and a send path, which the variant alone cannot tell apart: the state
machines, which render as invalid transition from X on event Y
whichever end asked for the transition, and the unknown-request errors.
Those land here because that is the safe direction: a failure that has
not been told apart is not evidence against a relay. The peer’s half
does not land here at all — it is Self::PeerViolation, which
AnyConnectionError::is_local answers false for.
The draft’s EndpointError is not carried through because it is one of
fourteen unrelated types with no shared spine; the message has it.
StreamReset(u64)
The peer abandoned this stream by resetting it, with the application error code it named.
Distinct from Self::StreamEnded and the distinction is the point: a
reset says the peer stopped on purpose, and several of the drafts’ rules
turn on exactly that. Section 2.2 forbids one Subgroup’s Objects on
different streams “unless one of the streams was reset prematurely” —
a sentence no caller can apply without being able to see a reset.
Stopped(u64)
The peer stopped reading this stream (STOP_SENDING), with its code.
SessionClosed(u64)
The peer closed the whole session, with the code it named.
Over MoQT that code is a draft’s own session error, which is the sharpest thing a refusal says.
StreamEnded
The stream ended and the peer did not reset it.
Every way a stream can run out short of a reset: a clean FIN with a read
still wanting bytes, a truncation, a closed stream. They are together
because no layer below this one tells them apart — ConnectionError
raises UnexpectedEnd for the first two alike — and putting a name on a
distinction that is not observable would invent it.
Transport
A transport error naming none of the above — a lost connection, a write that failed, a datagram that would not send.
Facade
This facade refused the call itself. Nothing was written and nothing reached the wire.
A value that will not fit the field the draft puts it in, a request
handle from a different draft than the connection, a draft whose feature
this build was compiled without, an object asked for before the header it
is framed against, a message handed to the control stream that belongs on
a request stream of its own. Local by construction, which is why
AnyConnectionError::is_local counts it: without a value saying so, a
facade refusal carries no prefix and reads to a caller exactly like a
relay hanging up.
PeerViolation
The peer broke a rule this endpoint enforces above its decoder.
The frame read perfectly well and is forbidden anyway, and the fact that forbids it is one of two kinds. Some are a comparison inside the frame: properties on an Object whose status permits none, a payload after a datagram header that permits none, a bidirectional stream opened with a message type the draft does not let one open with. The rest are a comparison against the session — a second GOAWAY, a Request ID out of the peer’s own sequence, a Track Alias already naming another track, an Object past the one the track ended at.
A decoder can see none of it, so none of it ever reaches Self::Codec
and a caller reading only that would find the peer blameless. Without
this variant the second group arrives as Self::Endpoint, which
AnyConnectionError::is_local counts as this side’s fault, so a
relay breaking one of these rules is filed against this build’s own
state machine.
close has exactly Self::Codec’s contract — the code this draft’s
own text names for the rule, or None where it states the rule and
attaches no consequence. It comes from the draft’s own
EndpointError::session_error_code or
Connection::codec_session_error_code, so the rule and its consequence
are never two readings of one sentence.
Fields
rule: AboveCodecRuleWhich rule, named the same way on every draft that states it.
Unclassified
A variant neither this facade nor its draft has classified.
Reachable only if the two tables disagree: a variant the match above does
not name, and that the draft’s own draft_specific_cause answered None
for. Both are exhaustive today — neither has a wildcard arm — so adding a
variant to a draft’s ConnectionError is a compile error in that draft’s
file rather than a silent arrival here.
Kept because the alternative in that arm is a panic, and a facade that
panics on an error is worse than one that declines to characterise it.
It answers AnyConnectionError::is_local false, which is the safe
direction: an unclassified failure is not evidence about anybody.