pub struct Connection {
transport: Transport,
endpoint: Endpoint,
draft: DraftVersion,
control_send: Option<FramedSendStream>,
control_recv: Option<FramedRecvStream>,
observer: Option<Box<dyn ConnectionObserver>>,
pending_events: Vec<ClientEvent>,
deferred_uni: Mutex<VecDeque<FramedRecvStream>>,
pending_inbound: Mutex<VecDeque<(FramedSendStream, FramedRecvStream)>>,
}Expand description
A live MoQT connection over QUIC or WebTransport, combining the endpoint state machine with actual network I/O.
Fields§
§transport: Transport§endpoint: Endpoint§draft: DraftVersion§control_send: Option<FramedSendStream>§control_recv: Option<FramedRecvStream>§observer: Option<Box<dyn ConnectionObserver>>§pending_events: Vec<ClientEvent>Setup events buffered during connect() and replayed when an
observer attaches via set_observer — without this, an observer
attached after connect returns would never see the handshake.
deferred_uni: Mutex<VecDeque<FramedRecvStream>>Unidirectional streams accepted while connect was looking for the
peer’s control stream, in arrival order.
Data streams are allowed to arrive before the control streams on this
draft, so the search cannot assume the first unidirectional stream is
the control one — and dropping the ones that are not would silently
lose objects the peer already sent.
accept_subgroup_stream empties
this before it accepts anything new.
Behind a mutex because that method takes &self. The lock is only
ever held for a pop_front, never across an await.
pending_inbound: Mutex<VecDeque<(FramedSendStream, FramedRecvStream)>>Bidirectional streams the peer opened that
accept_request_stream took off
the transport but did not finish reading a first message from, because
its future was dropped. In arrival order.
Without this a caller could not put accept_request_stream in a
select! at all: losing the race would lose a stream the peer had
already opened and, with it, whatever of the request had arrived.
accept_request_stream empties
this before it accepts anything new.
Behind a mutex for the same reason deferred_uni is: the lock is only
ever held for a push or a pop, never across an await.
Implementations§
Source§impl Connection
impl Connection
Sourcepub async fn connect(
addr: &str,
config: ClientConfig,
) -> Result<Self, ConnectionError>
pub async fn connect( addr: &str, config: ClientConfig, ) -> Result<Self, ConnectionError>
Connect to a MoQT server as a client.
Establishes a QUIC or WebTransport connection (based on
config.transport), brings up the control plane, performs the SETUP
handshake, and returns a ready-to-use connection.
§The control plane is a pair of unidirectional streams
Draft-17 Section 3.3: “MOQT uses a pair of unidirectional streams for
creating the session and exchanging control messages. Each peer opens
one control stream beginning with a SETUP message.” So each direction
is a separate stream opened by the peer that writes on it. This opens
one with open_uni and writes SETUP on it, then finds the peer’s by
accepting unidirectional streams until one leads with
CONTROL_STREAM_TYPE.
Nothing is written ahead of the SETUP: 0x2F00 is both the SETUP
message type and the unidirectional stream type for a control stream,
so the message’s own first field is the stream header. See
CONTROL_STREAM_TYPE.
A bidirectional stream is not the control stream here — the same section makes it a request stream, one that begins with TRACK_STATUS, SUBSCRIBE, PUBLISH, FETCH, PUBLISH_NAMESPACE or SUBSCRIBE_NAMESPACE: “Bidirectional streams MUST NOT begin with any other message type unless negotiated. If they do, the peer MUST close the Session with a PROTOCOL_VIOLATION.” A SETUP written on a bidirectional stream is exactly that case, so a peer that enforces the topology answers it by closing the session.
§Unidirectional streams that arrive before the peer’s control stream
They are kept, not dropped. Section 3.3 expects them: “Unidirectional
streams containing Objects or bidirectional stream(s) beginning with a
request message could arrive prior to the control streams, in which
case the data SHOULD be buffered until both control streams arrive and
setup is complete.” Each such stream is set aside and handed to
accept_subgroup_stream in arrival
order, ahead of any newly accepted stream. Only the leading type
varint is read from them here; the rest stays on the transport, unread
and still flow-controlled, so nothing is buffered in this process
beyond those few bytes.
One limit worth knowing: the search waits for each stream’s type varint in turn, so a peer that opens a unidirectional stream and then writes nothing on it stalls the handshake behind that stream.
Sourcepub async fn adopt(
transport: Transport,
config: ClientConfig,
) -> Result<Self, ConnectionError>
pub async fn adopt( transport: Transport, config: ClientConfig, ) -> Result<Self, ConnectionError>
Run the MoQT setup handshake over a transport somebody else established.
For choosing the draft from what the server selected: dial once through
crate::transport::dial_quic offering every ALPN, then bring the
connection to the module its answer names. Self::connect cannot do
this — it derives its single ALPN from the draft it was given.
config.draft must match this module. The transport is adopted as
given; nothing here re-checks the ALPN it was negotiated with.
Sourceasync fn connect_quic(
addr: &str,
config: &ClientConfig,
) -> Result<Transport, ConnectionError>
async fn connect_quic( addr: &str, config: &ClientConfig, ) -> Result<Transport, ConnectionError>
Establish a raw QUIC connection.
Offers this draft’s ALPN alone; crate::transport::dial_quic holds the
TLS and endpoint setup.
Sourceasync fn connect_webtransport(
_url: &str,
_config: &ClientConfig,
) -> Result<Transport, ConnectionError>
async fn connect_webtransport( _url: &str, _config: &ClientConfig, ) -> Result<Transport, ConnectionError>
Stub for when the webtransport feature is not enabled.
Sourcepub fn set_observer(&mut self, observer: Box<dyn ConnectionObserver>)
pub fn set_observer(&mut self, observer: Box<dyn ConnectionObserver>)
Attach an observer. Buffered handshake events from connect() are
flushed in arrival order before this returns.
Sourcepub fn clear_observer(&mut self)
pub fn clear_observer(&mut self)
Remove the observer.
Sourcefn emit(&self, event: ClientEvent)
fn emit(&self, event: ClientEvent)
Emit an event to the observer, if one is attached.
Sourcepub async fn send_control(
&mut self,
msg: &ControlMessage,
) -> Result<(), ConnectionError>
pub async fn send_control( &mut self, msg: &ControlMessage, ) -> Result<(), ConnectionError>
Send a control message on the control stream.
Wraps the draft-17 message in AnyControlMessage::Draft17 for
framing. This is the route for the messages that belong to the session
rather than to one request: GOAWAY, NAMESPACE, NAMESPACE_DONE,
PUBLISH_BLOCKED and REQUEST_UPDATE, the last of which carries its own
request id and is handled on the control stream by the peer’s endpoint.
SETUP is written by connect and is the control
stream’s own type varint.
§Requests are refused here
Draft-17 Section 3.3 moved requests off the control plane, in a sentence drafts 18 and 19 keep word for word: “In addition to the control streams, this specification uses bidirectional streams to carry requests.” The response comes back on that same bidirectional stream, and resetting it cancels the request (Section 3.3.1).
Which types may open one is not shared, so this method is written
against draft-17’s six and no further: they are RequestKind, and
draft-18 makes them seven by adding SUBSCRIBE_TRACKS.
Handing one of those six to this method returns
ConnectionError::RequestOnControlStream and writes nothing —
an enforcing peer sees no bytes at all, not a misplaced request. Use
the typed helpers, which open a bidirectional stream each:
subscribe, fetch,
joining_fetch, publish,
track_status,
publish_namespace and
subscribe_namespace.
Response types are still permitted. A response written here will be
refused by a conforming peer, whose endpoint answers a response on the
control stream with an error — but this is currently the only route
for them at all, since nothing yet accepts a request stream the peer
opened, and refusing them would remove capability rather than fix a
misdirected write. publish_done, the one
response with a helper, does not come through here: it takes the
request stream its PUBLISH opened.
Sourcepub async fn recv_control(&mut self) -> Result<ControlMessage, ConnectionError>
pub async fn recv_control(&mut self) -> Result<ControlMessage, ConnectionError>
Read the next control message from the control stream.
Returns the AnyControlMessage and also extracts the draft-17
ControlMessage for internal endpoint dispatch.
Sourcepub async fn recv_and_dispatch(
&mut self,
) -> Result<ControlMessage, ConnectionError>
pub async fn recv_and_dispatch( &mut self, ) -> Result<ControlMessage, ConnectionError>
Read and dispatch the next incoming control message through the endpoint state machine. Returns the decoded message for inspection.
Responses never arrive here. Draft-17 responses carry no request id
and belong on the request stream that asked for them, so the endpoint
refuses a response that turns up on the control stream. Read them with
recv_on_request_stream.
Sourceasync fn open_request_bi(
&self,
) -> Result<(FramedSendStream, FramedRecvStream), ConnectionError>
async fn open_request_bi( &self, ) -> Result<(FramedSendStream, FramedRecvStream), ConnectionError>
Open the bidirectional stream a request will be carried on.
Opened before the endpoint allocates a request id, so a transport
that refuses a new stream — the peer’s initial_max_streams_bidi is
exhausted, the connection is gone — costs nothing. The endpoint has no
way to abandon a request it has already allocated, so every failure
that can be moved ahead of the allocation is.
Nothing is written here. A request stream carries no stream-type
header: its first field is the leading message’s own type field, which
is what begin_request writes.
Sourcefn or_abandon<T>(
halves: &mut (FramedSendStream, FramedRecvStream),
built: Result<T, EndpointError>,
) -> Result<T, ConnectionError>
fn or_abandon<T>( halves: &mut (FramedSendStream, FramedRecvStream), built: Result<T, EndpointError>, ) -> Result<T, ConnectionError>
Reset a request stream that was opened but whose request could not be built, and pass the endpoint’s error through.
Without this, an endpoint refusal — the session is draining, the request-id range is exhausted — would leave a bidirectional stream open that never carries a first message, and dropping it would FIN it, telling the peer an empty stream ended cleanly.
Sourceasync fn begin_request(
&mut self,
halves: (FramedSendStream, FramedRecvStream),
kind: RequestKind,
request_id: VarInt,
msg: &ControlMessage,
) -> Result<RequestStream, ConnectionError>
async fn begin_request( &mut self, halves: (FramedSendStream, FramedRecvStream), kind: RequestKind, request_id: VarInt, msg: &ControlMessage, ) -> Result<RequestStream, ConnectionError>
Write msg as the first message on an opened bidirectional stream and
hand back the RequestStream that owns both halves.
This is the one place a request reaches the wire. Every request helper funnels through it, so the ordering — open, allocate, write, emit — is stated once.
A failed write resets both halves rather than leaving a half-written request stream behind. What it cannot undo is the endpoint’s allocation: the request id and its state machine already exist, and there is no way to retract them, so a write that fails here leaves one pending request the endpoint will never see answered.
Sourcepub async fn recv_on_request_stream(
&mut self,
stream: &mut RequestStream,
) -> Result<ControlMessage, ConnectionError>
pub async fn recv_on_request_stream( &mut self, stream: &mut RequestStream, ) -> Result<ControlMessage, ConnectionError>
Read the next message off a request stream and dispatch it through the endpoint with that stream’s own request id.
On draft-17 a response carries no request id; the stream is the correlation, so the id comes from the handle and not from the wire.
This blocks until a whole message has arrived. Backpressure is per
request: a stream nobody reads stays unread, and the peer stays flow
controlled on it alone. A peer that reset the stream surfaces as
ConnectionError::Transport carrying
TransportError::StreamReset with the peer’s code; a caller that is
deliberately not reading should watch
RequestStream::peer_cancelled instead.
§Errors
ConnectionError::Endpoint if the message is not one of this
draft’s response types, or if it does not fit the request’s state.
The message has already been emitted to the observer by then — what
arrived is reported whether or not the endpoint accepts it.
Sourcepub async fn send_on_request_stream(
&mut self,
stream: &mut RequestStream,
msg: &ControlMessage,
) -> Result<(), ConnectionError>
pub async fn send_on_request_stream( &mut self, stream: &mut RequestStream, msg: &ControlMessage, ) -> Result<(), ConnectionError>
Write a follow-up message on an already-open request stream.
The request itself was written when the stream was opened; this is for
what comes after it on the same stream, PUBLISH_DONE among them — see
publish_done, which uses this.
It does not refuse any message type. Which messages may follow a request on its own stream is not something this implementation can settle, so the choice is left to the caller rather than guessed at.
Sourcepub fn cancel_request_stream(
&mut self,
stream: &mut RequestStream,
code: u64,
) -> Result<(), ConnectionError>
pub fn cancel_request_stream( &mut self, stream: &mut RequestStream, code: u64, ) -> Result<(), ConnectionError>
Cancel a request: record it at the endpoint, then terminate its stream.
Section 3.3.1 puts the cancel at the stream — “Implementations SHOULD cancel requests by abruptly terminating any directions of a stream that are still open” — while the request’s own state lives in the endpoint, so the two have to move together. This is the only place that moves both.
The endpoint goes first and the stream is terminated only if it agrees,
which is the order every request path here uses: a caller acts on a
stream after the endpoint has accepted the step, never before. A refused
cancel therefore leaves the stream exactly as it was, and
RequestStream::cancel is still there for a caller that wants the
stream reset regardless.
Idempotent from both ends: a request that has already ended accepts the cancel and stays where it is, and a handle that has already been cancelled resets nothing a second time.
§Errors
ConnectionError::Endpoint if no request carries this stream’s id or
the request has not been written, and ConnectionError::Transport if
code is outside the QUIC varint range — see
RequestStream::cancel, which is what sends it.
Sourcepub async fn peer_cancelled_on_request_stream(
&mut self,
stream: &mut RequestStream,
) -> Result<Option<u64>, ConnectionError>
pub async fn peer_cancelled_on_request_stream( &mut self, stream: &mut RequestStream, ) -> Result<Option<u64>, ConnectionError>
Wait for the peer to cancel this request, and record it if it does.
RequestStream::peer_cancelled with the endpoint’s record attached. A
caller applying backpressure is deliberately not calling
recv_on_request_stream, which is the
other place a peer reset surfaces, so without this the request would end
on the wire and stay open in the endpoint’s record for as long as the
backpressure lasts.
Returns what the handle’s own method returns; see it for the Ok(None)
case and for what WebTransport can and cannot observe. Cancel-safe, and
it grants no flow-control credit.
Sourcepub async fn accept_request_stream(
&mut self,
) -> Result<(ControlMessage, RequestStream), ConnectionError>
pub async fn accept_request_stream( &mut self, ) -> Result<(ControlMessage, RequestStream), ConnectionError>
Accept the next bidirectional stream the peer opened, read the request it begins with, and hand back that request and a handle to answer it on.
This is the mirror of the request helpers. Where
subscribe and its siblings open a stream and write
a request, this takes one the peer opened and reads one. Draft-17
Section 3.3 puts requests in both directions on bidirectional streams,
so a client that only ever calls the helpers can never be published to
or subscribed from.
The returned RequestStream carries RequestOrigin::Peer. Answer
it with respond_subscribe_ok,
respond_fetch_ok,
respond_publish_ok,
respond_ok or
respond_error, and hold it for as long as
the request lasts — a subscription’s PUBLISH_DONE is written on it,
and dropping it resets the stream.
§Two refusals, two codes
Draft-17 Section 3.3, on a stream that begins with the wrong type: “Bidirectional streams MUST NOT begin with any other message type unless negotiated. If they do, the peer MUST close the Session with a PROTOCOL_VIOLATION.” Section 9.1, on the Request ID: “If an endpoint receives a Request ID where the least significant bit is incorrect for the sender, or a duplicate Request ID, it MUST close the session with INVALID_REQUEST_ID.” Both are closes of the session on the wire, with different codes, and both happen before this returns — the error handed back reports a session that is already gone, not one the caller must remember to close.
§Cancelling this future loses nothing
A stream taken off the transport but not yet read is put back on an
internal queue, and the next call takes it before accepting anything
new — including whatever bytes of the request had already arrived,
which live in the stream’s own reader. So this is safe to select!
against a shutdown signal or a timer. See
pending_inbound_count.
What it is not safe to do is run concurrently with another method
on the same connection: this takes &mut self because registering the
peer’s request moves endpoint state, and no signature avoids that while
the connection owns the endpoint. A caller blocked in
recv_on_request_stream waiting for
its own response is not accepting, and the peer’s request streams queue
up in the transport behind it. One loop that never blocks indefinitely
on a single read is the shape this supports.
§Ordering
The endpoint is told about the request last, after every step that can fail or be cancelled, and building the handle afterwards cannot fail. This is the inverse of the outbound path’s reasoning — it opens the stream before allocating a Request ID for the same reason — and rests on the same fact: the endpoint has no way to abandon a request it has already registered. Registering earlier would let a cancelled accept leave a state machine keyed to a stream nobody holds, and the peer’s next use of that Request ID would then be reported as a duplicate — a session close, over an id the peer used exactly once.
§Errors
ConnectionError::NonRequestOnRequestStream— the session has been closed with PROTOCOL_VIOLATION and the stream reset.ConnectionError::EndpointcarryingRequestIdorDuplicateRequestId— the session has been closed with INVALID_REQUEST_ID and the stream reset.ConnectionError::EndpointcarryingNotActiveorDraining— the stream is reset, the session is left alone.ConnectionError::TransportorConnectionError::Codec— the stream is reset, the session is left alone.
Sourcefn take_pending_inbound(&self) -> Option<(FramedSendStream, FramedRecvStream)>
fn take_pending_inbound(&self) -> Option<(FramedSendStream, FramedRecvStream)>
Take the oldest stream pair a cancelled
accept_request_stream put back, if any.
Synchronous on purpose, like
take_deferred_uni: the guard is dropped
before the caller awaits, so the lock is never held across a suspension
point.
Sourcepub fn pending_inbound_count(&self) -> usize
pub fn pending_inbound_count(&self) -> usize
How many peer-opened request streams a cancelled
accept_request_stream put back and a
later call has not yet taken.
Zero unless an accept future was dropped mid-read.
Sourceasync fn respond(
&mut self,
stream: &mut RequestStream,
msg: ControlMessage,
fin: bool,
) -> Result<(), ConnectionError>
async fn respond( &mut self, stream: &mut RequestStream, msg: ControlMessage, fin: bool, ) -> Result<(), ConnectionError>
Write msg as the response to the request stream carries, driving
the endpoint first and the wire second.
The response goes on the request’s own bidirectional stream and never on the control stream: draft-17 responses carry no Request ID, so the stream is the only thing that says what is being answered. Taking the id off the handle rather than from the caller makes that correlation unforgeable.
fin is true only for REQUEST_ERROR. See
respond_error.
Sourcepub async fn respond_ok(
&mut self,
stream: &mut RequestStream,
response: RequestOk,
) -> Result<(), ConnectionError>
pub async fn respond_ok( &mut self, stream: &mut RequestStream, response: RequestOk, ) -> Result<(), ConnectionError>
Answer a peer’s PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE or TRACK_STATUS with REQUEST_OK.
The send half is left open. A SUBSCRIBE_NAMESPACE responder still owes
the peer the namespaces it accepted, so finishing here would end the
request before it had been served; a TRACK_STATUS responder owes
nothing further and may call RequestStream::finish straight after.
§Errors
ConnectionError::RespondedToOwnRequest if stream is one this
endpoint opened, and ConnectionError::Endpoint if no request of a
kind REQUEST_OK answers is pending on it. Nothing is written either
way.
Sourcepub async fn respond_subscribe_ok(
&mut self,
stream: &mut RequestStream,
response: SubscribeOk,
) -> Result<(), ConnectionError>
pub async fn respond_subscribe_ok( &mut self, stream: &mut RequestStream, response: SubscribeOk, ) -> Result<(), ConnectionError>
Answer a peer’s SUBSCRIBE with SUBSCRIBE_OK.
The send half is left open, and it must be: this endpoint is now the
publisher of an established subscription and owes it a PUBLISH_DONE,
which travels on this same stream —
publish_done_on.
Sourcepub async fn respond_fetch_ok(
&mut self,
stream: &mut RequestStream,
response: FetchOk,
) -> Result<(), ConnectionError>
pub async fn respond_fetch_ok( &mut self, stream: &mut RequestStream, response: FetchOk, ) -> Result<(), ConnectionError>
Answer a peer’s FETCH with FETCH_OK.
The send half is left open. The fetched objects travel on separate
unidirectional streams, so a fetch responder may call
RequestStream::finish as soon as this returns; it is not done here
because nothing about FETCH_OK says the responder has no more to write.
Sourcepub async fn respond_publish_ok(
&mut self,
stream: &mut RequestStream,
response: PublishOk,
) -> Result<(), ConnectionError>
pub async fn respond_publish_ok( &mut self, stream: &mut RequestStream, response: PublishOk, ) -> Result<(), ConnectionError>
Answer a peer’s PUBLISH with PUBLISH_OK.
Draft-17 has PUBLISH_OK as a message of its own, 0x1E; drafts 18 and 19 folded it into REQUEST_OK. The send half is left open: accepting a PUBLISH establishes a subscription whose PUBLISH_DONE arrives on this stream.
Sourcepub async fn respond_error(
&mut self,
stream: &mut RequestStream,
response: RequestError,
) -> Result<(), ConnectionError>
pub async fn respond_error( &mut self, stream: &mut RequestStream, response: RequestError, ) -> Result<(), ConnectionError>
Reject a peer’s request with REQUEST_ERROR, and finish the send half.
The FIN is part of the act, not a convenience: draft-17 Section 3.3.1 says “When an endpoint rejects a request without performing any application processing, it SHOULD send a REQUEST_ERROR and FIN the stream.” It is also the one response that can be finished immediately, because a rejected request leaves nothing further to send — every success path owes the peer something more.
A finished handle does nothing further on Drop, so the rejected
stream is not then reset.
Sourcepub async fn publish_done_on(
&mut self,
stream: &mut RequestStream,
status_code: VarInt,
stream_count: VarInt,
reason_phrase: Vec<u8>,
) -> Result<(), ConnectionError>
pub async fn publish_done_on( &mut self, stream: &mut RequestStream, status_code: VarInt, stream_count: VarInt, reason_phrase: Vec<u8>, ) -> Result<(), ConnectionError>
End a subscription this endpoint accepted, on the stream the peer’s SUBSCRIBE opened.
The mirror of publish_done, which ends a
publication this endpoint offered with PUBLISH. Both write PUBLISH_DONE
on a request stream and take the Request ID off the handle; they differ
in which state machine moves, and therefore in which one refuses.
Sourcepub async fn subscribe(
&mut self,
track_namespace: TrackNamespace,
track_name: Vec<u8>,
parameters: Vec<KeyValuePair>,
) -> Result<RequestStream, ConnectionError>
pub async fn subscribe( &mut self, track_namespace: TrackNamespace, track_name: Vec<u8>, parameters: Vec<KeyValuePair>, ) -> Result<RequestStream, ConnectionError>
Send a SUBSCRIBE on a bidirectional stream of its own.
The returned RequestStream is where SUBSCRIBE_OK, REQUEST_ERROR
and later PUBLISH_DONE arrive — read them with
recv_on_request_stream. Hold it for
the subscription’s life: dropping it resets the stream, which
cancels the subscription.
Sourcepub async fn fetch(
&mut self,
track_namespace: TrackNamespace,
track_name: Vec<u8>,
start_group: VarInt,
start_object: VarInt,
end_group: VarInt,
end_object: VarInt,
parameters: Vec<KeyValuePair>,
) -> Result<RequestStream, ConnectionError>
pub async fn fetch( &mut self, track_namespace: TrackNamespace, track_name: Vec<u8>, start_group: VarInt, start_object: VarInt, end_group: VarInt, end_object: VarInt, parameters: Vec<KeyValuePair>, ) -> Result<RequestStream, ConnectionError>
Send a standalone FETCH on a bidirectional stream of its own.
FETCH_OK or REQUEST_ERROR comes back on the returned
RequestStream; the fetched objects arrive on separate
unidirectional data streams. Dropping the handle cancels the fetch.
Sourcepub async fn joining_fetch(
&mut self,
joining_request_id: VarInt,
joining_start: VarInt,
parameters: Vec<KeyValuePair>,
) -> Result<RequestStream, ConnectionError>
pub async fn joining_fetch( &mut self, joining_request_id: VarInt, joining_start: VarInt, parameters: Vec<KeyValuePair>, ) -> Result<RequestStream, ConnectionError>
Send a Relative Joining Fetch (Fetch Type 0x2) on a bidirectional stream of its own.
A joining FETCH names an existing subscription’s request id but is still a FETCH, so it opens its own request stream rather than sharing the subscription’s.
joining_start counts groups back from the subscription’s largest
group. To name the starting group outright, use
absolute_joining_fetch.
Sourcepub async fn absolute_joining_fetch(
&mut self,
joining_request_id: VarInt,
joining_start: VarInt,
parameters: Vec<KeyValuePair>,
) -> Result<RequestStream, ConnectionError>
pub async fn absolute_joining_fetch( &mut self, joining_request_id: VarInt, joining_start: VarInt, parameters: Vec<KeyValuePair>, ) -> Result<RequestStream, ConnectionError>
Send an Absolute Joining Fetch (Fetch Type 0x3) on a bidirectional stream of its own.
Here joining_start is the group to begin at rather than an offset:
draft-17 Section 9.14.2.1 has the publisher set the Start Location to
{Joining Start, 0}.
Sourcepub async fn subscribe_namespace(
&mut self,
namespace_prefix: TrackNamespace,
subscribe_options: VarInt,
parameters: Vec<KeyValuePair>,
) -> Result<RequestStream, ConnectionError>
pub async fn subscribe_namespace( &mut self, namespace_prefix: TrackNamespace, subscribe_options: VarInt, parameters: Vec<KeyValuePair>, ) -> Result<RequestStream, ConnectionError>
Send a SUBSCRIBE_NAMESPACE on a bidirectional stream of its own.
subscribe_options is draft-17’s; draft-18 removed the field.
Sourcepub async fn publish_namespace(
&mut self,
track_namespace: TrackNamespace,
parameters: Vec<KeyValuePair>,
) -> Result<RequestStream, ConnectionError>
pub async fn publish_namespace( &mut self, track_namespace: TrackNamespace, parameters: Vec<KeyValuePair>, ) -> Result<RequestStream, ConnectionError>
Send a PUBLISH_NAMESPACE on a bidirectional stream of its own.
Sourcepub async fn track_status(
&mut self,
track_namespace: TrackNamespace,
track_name: Vec<u8>,
parameters: Vec<KeyValuePair>,
) -> Result<RequestStream, ConnectionError>
pub async fn track_status( &mut self, track_namespace: TrackNamespace, track_name: Vec<u8>, parameters: Vec<KeyValuePair>, ) -> Result<RequestStream, ConnectionError>
Send a TRACK_STATUS on a bidirectional stream of its own.
Sourcepub async fn publish(
&mut self,
track_namespace: TrackNamespace,
track_name: Vec<u8>,
track_alias: VarInt,
parameters: Vec<KeyValuePair>,
track_properties: Vec<KeyValuePair>,
) -> Result<RequestStream, ConnectionError>
pub async fn publish( &mut self, track_namespace: TrackNamespace, track_name: Vec<u8>, track_alias: VarInt, parameters: Vec<KeyValuePair>, track_properties: Vec<KeyValuePair>, ) -> Result<RequestStream, ConnectionError>
Send a PUBLISH on a bidirectional stream of its own.
PUBLISH_OK or REQUEST_ERROR comes back on the returned
RequestStream, and publish_done is written
back on it when the publication ends — so the handle must be held for
as long as the publication lasts.
Sourcepub async fn publish_done(
&mut self,
stream: &mut RequestStream,
status_code: VarInt,
stream_count: VarInt,
reason_phrase: Vec<u8>,
) -> Result<(), ConnectionError>
pub async fn publish_done( &mut self, stream: &mut RequestStream, status_code: VarInt, stream_count: VarInt, reason_phrase: Vec<u8>, ) -> Result<(), ConnectionError>
Send a PUBLISH_DONE on the request stream the PUBLISH opened.
PUBLISH_DONE is a response and carries no request id on the wire, so
the stream is the only thing that says which publication ended. The id
the endpoint needs is taken off stream, which makes the correlation
unforgeable — there is no way to name one request and write on
another’s stream.
Sourcepub async fn open_subgroup_stream(
&self,
header: &AnySubgroupHeader,
) -> Result<FramedSendStream, ConnectionError>
pub async fn open_subgroup_stream( &self, header: &AnySubgroupHeader, ) -> Result<FramedSendStream, ConnectionError>
Open a new unidirectional stream for sending subgroup data.
Sourcepub async fn open_fetch_stream(
&self,
header: &AnyFetchHeader,
) -> Result<FramedSendStream, ConnectionError>
pub async fn open_fetch_stream( &self, header: &AnyFetchHeader, ) -> Result<FramedSendStream, ConnectionError>
Open a new unidirectional stream for sending a FETCH’s objects.
The objects answering a FETCH do not go on the request’s own stream:
they go on a unidirectional stream of their own, which opens with a
FETCH_HEADER naming the request they belong to. This writes that header
and hands back the stream, the same way
open_subgroup_stream does for a
subgroup.
The caller owns the stream that comes back. Nothing here remembers which request it belongs to, so an endpoint serving several fetches at once keeps its own map from Request ID to stream.
Sourcepub async fn accept_subgroup_stream(
&self,
) -> Result<(AnySubgroupHeader, FramedRecvStream), ConnectionError>
pub async fn accept_subgroup_stream( &self, ) -> Result<(AnySubgroupHeader, FramedRecvStream), ConnectionError>
Accept an incoming unidirectional data stream and read its subgroup header.
Streams the peer opened before its control stream are returned first,
in arrival order, before any new one is accepted from the transport:
connect had to look at them to find the control
stream and set the rest aside rather than drop them. They are
otherwise ordinary — the type varint connect read is still on the
front of each one.
Sourcepub async fn accept_fetch_stream(
&self,
) -> Result<(AnyFetchHeader, FramedRecvStream), ConnectionError>
pub async fn accept_fetch_stream( &self, ) -> Result<(AnyFetchHeader, FramedRecvStream), ConnectionError>
Accept the next unidirectional stream and read its fetch header.
accept_subgroup_stream’s twin. The two
are separate because the header decides how every object after it is
framed, so a caller has to know which it is expecting before the first
byte is read.
Objects come off the returned stream with
FramedRecvStream::read_fetch_object.
Sourcefn take_deferred_uni(&self) -> Option<FramedRecvStream>
fn take_deferred_uni(&self) -> Option<FramedRecvStream>
Take the oldest stream connect set aside, if any.
Synchronous on purpose: the guard is dropped before the caller awaits,
so the lock is never held across a suspension point. A poisoned lock
is recovered rather than propagated — nothing here can leave the queue
in a state a later reader could be misled by, since the only mutation
is a pop_front.
Sourcepub fn deferred_stream_count(&self) -> usize
pub fn deferred_stream_count(&self) -> usize
How many unidirectional streams connect set aside
and accept_subgroup_stream has not
yet handed back.
Zero for a peer that opened its control stream first, which is the ordinary case.
Sourcepub fn send_datagram(
&self,
header: &AnyDatagramHeader,
payload: &[u8],
) -> Result<(), ConnectionError>
pub fn send_datagram( &self, header: &AnyDatagramHeader, payload: &[u8], ) -> Result<(), ConnectionError>
Send an object via datagram.
The header goes through AnyDatagramHeader::encode, which refuses a
header whose Object Status the framing it names cannot carry. Such a
header errors here and nothing is sent, rather than going out as an
ordinary payload datagram with the status quietly dropped.
Sourcepub async fn recv_datagram(
&self,
) -> Result<(AnyDatagramHeader, Bytes), ConnectionError>
pub async fn recv_datagram( &self, ) -> Result<(AnyDatagramHeader, Bytes), ConnectionError>
Receive a datagram and decode its header.
Sourcepub fn endpoint_mut(&mut self) -> &mut Endpoint
pub fn endpoint_mut(&mut self) -> &mut Endpoint
Mutable access to the endpoint state machine.
Sourcepub fn draft(&self) -> DraftVersion
pub fn draft(&self) -> DraftVersion
Returns the draft version this connection is using.
Sourcefn close_for(&self, err: &EndpointError)
fn close_for(&self, err: &EndpointError)
Close the session on the wire when the endpoint says a violation is fatal to it.
EndpointError::session_error_code answers Some for exactly the
errors draft-17 tells the receiver to close the session over, and the
endpoint has already moved its own state machine to Closed by the time
this runs. Without this step that move was purely internal: the local
endpoint refused to start anything new while the peer, which is the one
that broke the rule, saw a session that was still open and went on
sending. “MUST close the session with a PROTOCOL_VIOLATION” is a
statement about the wire, so it takes a CONNECTION_CLOSE to satisfy it.
The reason phrase is the error’s own Display text, which names the
message and the rule rather than repeating the numeric code the close
already carries.
Errors that answer None are recoverable and nothing is sent.
Sourcefn close_if_session_fatal(&self, err: EndpointError) -> ConnectionError
fn close_if_session_fatal(&self, err: EndpointError) -> ConnectionError
close_for, then the error unchanged, for the
common case where the endpoint’s error is also what the caller returns.
Sourcefn codec_session_error_code(err: &CodecError) -> Option<SessionErrorCode>
fn codec_session_error_code(err: &CodecError) -> Option<SessionErrorCode>
The code to close the session with when a control message could not be decoded because the peer broke a rule draft-17 answers with a close.
Every variant listed here comes from a sentence in the draft that names the consequence: the reason phrase and GOAWAY URI maxima (Sections 1.4.4 and 9.5), the KVP value maximum and the delta-encoded type overflow (Section 1.4.3), the duplicate-parameter rule (Section 9.3), and the Track Namespace field, count and length rules (Section 2.4.1). Each of those reads “MUST close the session with a PROTOCOL_VIOLATION”. The Required Request ID Delta bound of Section 9.2 is the one that names a different code, INVALID_REQUIRED_REQUEST_ID.
One more rule reaches this table without naming a code: “An endpoint that receives an unknown message type MUST close the session”, stated in those words by all thirteen drafts. Protocol Violation is what carries it, as it does on every draft below this one.
CodecError::ObjectIdOverflow is absent on purpose, and this is the
one draft where that is so. Section 10.4.2 gives the same Object ID
delta arithmetic drafts 18 and 19 give, and the codec reports the wrap
on all three, but only those two go on to say “the endpoint MUST close
the session with a PROTOCOL_VIOLATION”. Draft-17 states no consequence,
so the wrap stops here at a refused frame rather than a closed session.
None for everything else, including CodecError::InvalidField. That
variant is shared by a dozen unrelated malformations, only some of which
the draft answers with a close, so treating it as fatal would close
sessions the draft does not ask to be closed. Splitting it is the way to
bring the rest of those rules under this function; widening the match is
not.
Sourcefn close_for_codec(&self, err: ConnectionError) -> ConnectionError
fn close_for_codec(&self, err: ConnectionError) -> ConnectionError
Close the session on the wire when a decode failure is one draft-17 answers with a close, and hand the error back unchanged.
The codec’s counterpart to
close_if_session_fatal. Without it
every bound the decoder enforces would stop at this endpoint refused the
frame while the peer, which is the one that broke the rule, saw a
session that was still open and went on sending. “MUST close the session
with a PROTOCOL_VIOLATION” is a statement about the wire.
Sourcepub fn requests_to_cancel(&self, err: &ConnectionError) -> Vec<VarInt>
pub fn requests_to_cancel(&self, err: &ConnectionError) -> Vec<VarInt>
Name every request whose stream the caller must reset, for a track a data path has just found malformed.
Section 2.4.2 answers its whole list of conditions at once: “it MUST cancel any corresponding subscription or fetches for that Track from that publisher”. On this draft cancelling a request is a transport operation rather than a message — Section 3.3.1: “Implementations SHOULD cancel requests by abruptly terminating any directions of a stream that are still open using RESET_STREAM / RESET_STREAM_AT or STOP_SENDING.”
A SHOULD on this draft and on draft-18; draft-19 drops the keyword and states it outright.
§Why this returns ids instead of doing it
Because the streams are the caller’s. Every request on this draft lives
at the front of a bidirectional stream of its own, and
Connection::recv_on_request_stream hands that stream back as a
RequestStream. There is no handle here to reset. So the connection
does the half it can — note the track, and work out which requests
receive it — and the caller passes each id to
Connection::cancel_request_stream, which resets the stream and
moves the endpoint’s record.
This is the one place in this crate where the two halves of an answer
are split across the API boundary, and it is the draft that splits
them: drafts 12 through 16 answer with a control message, which the
connection owns, so withdraw_for_data_stream there does the whole
thing.
§Both data paths come here
Unlike the drafts that answer with a message, where a datagram is read through the connection and answers itself. Here neither path can, for the same reason, so there is one entry point rather than two. Pass it whatever error a read returned; anything that is not this condition gives back an empty list.
Empty is not “the track was fine” — it is also what an alias no live binding names gives, and what a track this endpoint only publishes gives.
Sourcepub fn close_for_data_stream(&self, err: &ConnectionError) -> bool
pub fn close_for_data_stream(&self, err: &ConnectionError) -> bool
Close the session when a failure raised while reading a data stream is one draft-17 answers with a close. Reports whether it closed.
recv_control does this for itself, because it
owns both the stream and the connection. A data stream does not:
accept_subgroup_stream hands the caller
a FramedRecvStream, which holds no connection and so cannot close
one, and the reads that raise these failures happen there. The caller is
the only party holding both halves, which is what this is for.
Splitting it this way rather than closing inside the reader keeps a caller that is deliberately permissive — a tool reproducing a capture, say — able to read a violating stream and report it without tearing the session down. The rule is stated at endpoints, and this is where an endpoint decides it is one.
On this draft only one rule reaches here: properties beside a status
that is not Normal, Section 10.2.1.2. Drafts 18 and 19 also answer the
Object ID delta wrap of their Section 11.4.2, which draft-17 describes
without stating a consequence — see
codec_session_error_code.