Skip to main content

ErrorCause

Enum ErrorCause 

Source
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::Codec carries 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: CodecError

The decoder’s own error, unflattened.

§close: Option<u64>

The code the negotiated draft answers it with, where it names one.

§

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: AboveCodecRule

Which rule, named the same way on every draft that states it.

§close: Option<u64>

The code the negotiated draft answers it with, where it names one.

§

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.

Trait Implementations§

Source§

impl Clone for ErrorCause

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ErrorCause

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for ErrorCause

Source§

impl PartialEq for ErrorCause

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for ErrorCause

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more