joule-moq-relay: a WebTransport session closes with its MOQT code and reason (WT_CLOSE_SESSION), on a vendored wtransport 0.7.2 #365
36 changed files with 9947 additions and 41 deletions
9
.github/workflows/joule-code-conformance.yml
vendored
9
.github/workflows/joule-code-conformance.yml
vendored
|
|
@ -204,6 +204,15 @@ jobs:
|
|||
shell: bash
|
||||
run: cargo test --locked -p joule-moq-relay --all-targets
|
||||
|
||||
# The relay's WebTransport layer, wtransport 0.7.2 vendored with a session close
|
||||
# (vendor/wtransport/VENDORED.md): its unit tests, the WT_CLOSE_SESSION capsule's bytes
|
||||
# against the layout of draft-ietf-webtrans-http3-16 §6 among them. It is not a member
|
||||
# of the workspace, so `--all-targets` above does not reach it.
|
||||
- name: The vendored wtransport's unit tests
|
||||
working-directory: joule-code/joule-code-rs
|
||||
shell: bash
|
||||
run: cargo test --locked -p wtransport --lib
|
||||
|
||||
# The WHIP and WHEP gateway. Every test is on loopback, against a relay that checks
|
||||
# every receipt at its door: a WHIP publisher (str0m as the client) of H.264 and Opus
|
||||
# whose frames a relay subscriber receives whole with every receipt verified, and DELETE
|
||||
|
|
|
|||
1
.github/workflows/joule-moq-room.yml
vendored
1
.github/workflows/joule-moq-room.yml
vendored
|
|
@ -23,6 +23,7 @@ on:
|
|||
- 'joule-code/joule-code-rs/crates/joule-moq/**'
|
||||
- 'joule-code/joule-code-rs/crates/joule-moq-relay/**'
|
||||
- 'joule-code/joule-code-rs/crates/jwp/**'
|
||||
- 'joule-code/joule-code-rs/vendor/**'
|
||||
- 'wai/wai-rs/**'
|
||||
- 'wai/wai-ops/**'
|
||||
- '.github/workflows/joule-moq-room.yml'
|
||||
|
|
|
|||
2
.github/workflows/joule-moq-web.yml
vendored
2
.github/workflows/joule-moq-web.yml
vendored
|
|
@ -43,6 +43,7 @@ on:
|
|||
- 'joule-code/joule-code-rs/crates/jwp/**'
|
||||
- 'joule-code/joule-code-rs/Cargo.toml'
|
||||
- 'joule-code/joule-code-rs/Cargo.lock'
|
||||
- 'joule-code/joule-code-rs/vendor/**'
|
||||
- 'wai/wai-live-web/**'
|
||||
- 'wai/wai-rs/**'
|
||||
- 'wai/wai-ops/**'
|
||||
|
|
@ -57,6 +58,7 @@ on:
|
|||
- 'joule-code/joule-code-rs/crates/jwp/**'
|
||||
- 'joule-code/joule-code-rs/Cargo.toml'
|
||||
- 'joule-code/joule-code-rs/Cargo.lock'
|
||||
- 'joule-code/joule-code-rs/vendor/**'
|
||||
- 'wai/wai-live-web/**'
|
||||
- 'wai/wai-rs/**'
|
||||
- 'wai/wai-ops/**'
|
||||
|
|
|
|||
|
|
@ -4,6 +4,38 @@ All notable changes to the Joule Code standard are recorded here. The format fol
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed — `joule-moq-relay`: a session over WebTransport closes with its code and reason
|
||||
|
||||
- **The relay closes a WebTransport session, not the QUIC connection under it**
|
||||
(`draft-ietf-moq-transport-22` §6.6): a WT_CLOSE_SESSION capsule on the CONNECT stream
|
||||
carries the MOQT session termination code as the session's 32-bit application error code,
|
||||
unmapped, and the reason (`draft-ietf-webtrans-http3-16` §6); the QUIC connection closes
|
||||
after the peer has closed its side of that stream, or two seconds on. A page now reads the
|
||||
relay's code and reason over WebTransport as it does over the WebSocket binding: a grant
|
||||
presented without its proof, for one, is `UNAUTHORIZED` (2) with its live-rooms §10.5
|
||||
reason, where the page saw only that the session was lost.
|
||||
- **Nothing of the session overtakes its close.** A stream the relay lets go once a
|
||||
WebTransport session is closing is held until the QUIC connection has closed, so no FIN or
|
||||
STOP_SENDING reaches the page ahead of the capsule (a page whose stream was stopped just
|
||||
before the close was seen to lose its renderer); a stream the relay refuses before SETUP
|
||||
is kept until then too.
|
||||
- **A peer's close is read the same way.** `closed()` over WebTransport gives the code and
|
||||
reason of the peer's WT_CLOSE_SESSION (`by_peer: true`), so a page's close code reaches
|
||||
the session's signed summary; a QUIC connection closed with the session still open gives
|
||||
no MOQT code (its code is HTTP/3's).
|
||||
- **`wtransport` 0.7.2 is vendored** (`joule-code-rs/vendor/wtransport`, MIT OR
|
||||
Apache-2.0, patched in for the joule-code workspace and `joule-moq-room`), because the
|
||||
release reads WT_CLOSE_SESSION but cannot send it. The change gives it
|
||||
`Connection::close_session` and `ConnectionError::SessionClosed`, and makes `closed()`
|
||||
report the session's end; `VENDORED.md` lists every change and why. Its unit tests, the
|
||||
capsule's bytes against the draft's layout among them, run in the relay's CI job.
|
||||
- **Tests.** `tests/webtransport_close.rs`: the relay refusing a grant without its proof,
|
||||
refusing a stream that breaks a rule before SETUP (with no STOP_SENDING ahead of the
|
||||
close), refusing a session past its session count, and ending one at its time limit, each
|
||||
read by the client as the exact code and reason; and a client's own close, recorded as the
|
||||
peer's in the relay's summary. The browser end-to-end check of a bare grant now requires
|
||||
code 2 and the reason over WebTransport too.
|
||||
|
||||
### Added — DVR on the relay (L7b): `joule-moq-relay`'s DVR store, the seek power, the room page's Seek back
|
||||
|
||||
- **`joule-moq-relay` keeps a DVR window on disk** (`[dvr]`, `[[dvr.window]]`; WAI
|
||||
|
|
|
|||
2
joule-code/joule-code-rs/Cargo.lock
generated
2
joule-code/joule-code-rs/Cargo.lock
generated
|
|
@ -4648,8 +4648,6 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
|||
[[package]]
|
||||
name = "wtransport"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4273ce3157a3262a68665f8d3f20a0ac0c5b8a69ffd67f05ae986832ebec036"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"pem",
|
||||
|
|
|
|||
|
|
@ -50,3 +50,9 @@ serde_json = "1"
|
|||
base64 = "0.22"
|
||||
hex = "0.4"
|
||||
thiserror = "2"
|
||||
|
||||
[patch.crates-io]
|
||||
# wtransport 0.7.2 with a WebTransport session close: a CLOSE_WEBTRANSPORT_SESSION
|
||||
# capsule on the CONNECT stream (draft-ietf-webtrans-http3 §6), which the release
|
||||
# cannot send. What was changed, and why, is in vendor/wtransport/VENDORED.md.
|
||||
wtransport = { path = "vendor/wtransport" }
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ a room grant signs (below, *Tokens and room grants*).
|
|||
Native QUIC and WebTransport share one quinn endpoint: the relay reads the ALPN of each
|
||||
connection and either runs MOQT on it directly or hands it to the WebTransport layer.
|
||||
|
||||
**The WebTransport implementation** is `wtransport` 0.7 (MIT OR Apache-2.0). It was
|
||||
chosen because it runs on the same quinn 0.11 as `joule-moq`, so one UDP endpoint serves
|
||||
both ALPNs; it gives the server the CONNECT request's headers and lets it answer with its
|
||||
**The WebTransport implementation** is `wtransport` 0.7.2 (MIT OR Apache-2.0), vendored
|
||||
(below). It was chosen because it runs on the same quinn 0.11 as `joule-moq`, so one UDP
|
||||
endpoint serves both ALPNs; it gives the server the CONNECT request's headers and lets it answer with its
|
||||
own, which is what `WT-Available-Protocols` and `WT-Protocol` need; and its client checks
|
||||
a certificate by hash under the rules a browser applies to `serverCertificateHashes`
|
||||
(ECDSA P-256, a validity of fourteen days at most), so the relay's tests connect the way a
|
||||
|
|
@ -49,6 +49,28 @@ page does. Stream bytes are read and written on the quinn streams underneath; ap
|
|||
error codes on WebTransport streams are mapped into the HTTP/3 error space as
|
||||
`draft-ietf-webtrans-http3-16` §4.4 gives.
|
||||
|
||||
**Closing a WebTransport session.** A MOQT session over WebTransport is closed by closing
|
||||
the WebTransport session (`draft-ietf-moq-transport-22` §6.6). The relay sends a
|
||||
`WT_CLOSE_SESSION` capsule (`0x2843`) on the session's CONNECT stream, then a FIN
|
||||
(`draft-ietf-webtrans-http3-16` §6): the MOQT session termination code (§12.2) is the
|
||||
capsule's 32-bit application error code as it stands, not mapped into the HTTP/3 space as
|
||||
stream codes are, and the reason, at most 1,024 bytes of UTF-8, is its message. The QUIC
|
||||
connection is closed with `H3_NO_ERROR` once the peer has closed its side of the CONNECT
|
||||
stream, or two seconds after the capsule, so the connection's close cannot overtake it.
|
||||
Nothing else of the session overtakes it either: a stream the relay lets go once the session
|
||||
is closing (the client's control stream of a refused session, say) is held until the QUIC
|
||||
connection has closed, rather than ended at once with a FIN or STOP_SENDING that would reach
|
||||
the page before the capsule; a page whose stream was stopped just ahead of the close was
|
||||
seen to lose its renderer. A page reads the code and reason from `WebTransport.closed`
|
||||
(`closeCode`, `reason`), as it reads them from the WebSocket binding's `WT_CLOSE_SESSION`. A session the client closes is
|
||||
read the same way: the relay's `closed()` gives the client's code and reason, and the
|
||||
session's summary records the code as the peer's. A QUIC connection that closes with the
|
||||
session still open gives no MOQT code; its code is an HTTP/3 error code.
|
||||
|
||||
The release of `wtransport` reads `WT_CLOSE_SESSION` but cannot send it, so the joule-code
|
||||
workspace vendors 0.7.2 with a session close added (`vendor/wtransport`, patched in with
|
||||
`[patch.crates-io]`; `vendor/wtransport/VENDORED.md` lists what changed and why).
|
||||
|
||||
## How a browser connects
|
||||
|
||||
**WebTransport, in development.** Start the relay with `--dev` (or with no certificate
|
||||
|
|
|
|||
|
|
@ -120,24 +120,33 @@ pub fn fetch_range(f: Option<LocationFilter>, largest: Location) -> Option<(Loca
|
|||
|
||||
// ── The session ────────────────────────────────────────────────────────────
|
||||
|
||||
/// The client's SETUP, from its control stream; data streams that come first wait in
|
||||
/// `early`. A stream that breaks a rule is put there too, so that it ends after the
|
||||
/// session's close, not before it (`transport::webtransport` holds what is dropped then).
|
||||
async fn read_setup(conn: &SharedConnection, max_payload: u64, early: &mut Vec<UniReader>) -> Result<(Setup, UniReader), String> {
|
||||
loop {
|
||||
let recv = conn.accept_uni().await.map_err(|e| e.to_string())?;
|
||||
let mut r = UniReader::new(recv, max_payload);
|
||||
match r.start().await.map_err(|e| e.to_string())? {
|
||||
StreamHeader::Control => {
|
||||
return match r.next().await.map_err(|e| e.to_string())? {
|
||||
Some(UniEvent::Control(ControlMessage::Setup(s))) => Ok((s, r)),
|
||||
_ => Err("the control stream does not begin with SETUP".into()),
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
if early.len() >= EARLY_STREAMS {
|
||||
return Err("too many data streams before SETUP".into());
|
||||
}
|
||||
let first = match r.start().await.map_err(|e| e.to_string()) {
|
||||
Ok(StreamHeader::Control) => r.next().await.map_err(|e| e.to_string()),
|
||||
Ok(_) if early.len() < EARLY_STREAMS => {
|
||||
early.push(r);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Ok(_) => Err("too many data streams before SETUP".into()),
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
return match first {
|
||||
Ok(Some(UniEvent::Control(ControlMessage::Setup(s)))) => Ok((s, r)),
|
||||
Ok(_) => {
|
||||
early.push(r);
|
||||
Err("the control stream does not begin with SETUP".into())
|
||||
}
|
||||
Err(e) => {
|
||||
early.push(r);
|
||||
Err(e)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,32 @@
|
|||
//! bytes are read and written on the quinn stream. Application error codes on WebTransport
|
||||
//! streams are 32-bit and ride RESET_STREAM and STOP_SENDING mapped into the HTTP/3 error
|
||||
//! space (webtrans-http3 §4.4); [`wt_to_h3`] and [`h3_to_wt`] are that mapping.
|
||||
//!
|
||||
//! The session closes by closing the WebTransport session (moq-transport-22 §6.6): a
|
||||
//! WT_CLOSE_SESSION capsule on the CONNECT stream carries the MOQT session termination code
|
||||
//! as the session's 32-bit application error code, unmapped, and the reason
|
||||
//! (webtrans-http3 §6). The vendored wtransport sends it (`vendor/wtransport/VENDORED.md`),
|
||||
//! and reports the one a peer sends, so a browser reads the relay's code and reason as it
|
||||
//! does over the WebSocket binding. The QUIC connection closes after; its code is an HTTP/3
|
||||
//! error code, not the session's.
|
||||
//!
|
||||
//! The capsule must reach the peer before any of the session's streams ends: §6 ends them
|
||||
//! after the session, and a page whose stream was stopped just ahead of the close was seen
|
||||
//! to lose its renderer. A stream dropped once the session is closing would send its FIN or
|
||||
//! STOP_SENDING at once, so it is held (`Closing`) until the QUIC connection has closed,
|
||||
//! when it ends with nothing more on the wire.
|
||||
|
||||
use super::quic::{conn_err, READ_CHUNK};
|
||||
use super::{BoxRecv, BoxSend, Connection, RecvStream, SendStream, TResult, TransportError, TransportKind};
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use wtransport::VarInt;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
/// How long the streams of a closed session are held at most: past the vendored driver's
|
||||
/// wait for the peer (two seconds) with room to spare.
|
||||
const HOLD: Duration = Duration::from_secs(10);
|
||||
|
||||
/// The first HTTP/3 error code of the WebTransport application range.
|
||||
const WT_FIRST: u64 = 0x52e4_a40f_a8db;
|
||||
|
|
@ -68,11 +88,29 @@ pub fn select_protocol(header: Option<&str>) -> Option<&'static str> {
|
|||
pub struct WtConnection {
|
||||
conn: wtransport::Connection,
|
||||
target: Option<String>,
|
||||
closing: Arc<Closing>,
|
||||
}
|
||||
|
||||
/// The streams dropped while a session this end closed is closing, held until its QUIC
|
||||
/// connection has closed (module docs).
|
||||
#[derive(Default)]
|
||||
struct Closing {
|
||||
holding: AtomicBool,
|
||||
held: Mutex<Vec<Box<dyn Send>>>,
|
||||
}
|
||||
|
||||
impl Closing {
|
||||
/// Keep a stream while the session is closing; otherwise it is dropped here, as usual.
|
||||
fn drop_or_hold(&self, h: Box<dyn Send>) {
|
||||
if self.holding.load(Ordering::Acquire) {
|
||||
self.held.lock().unwrap().push(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WtConnection {
|
||||
pub fn new(conn: wtransport::Connection) -> Self {
|
||||
WtConnection { conn, target: None }
|
||||
WtConnection { conn, target: None, closing: Arc::default() }
|
||||
}
|
||||
|
||||
/// The `//authority/path` of the CONNECT that opened the session.
|
||||
|
|
@ -85,23 +123,71 @@ impl WtConnection {
|
|||
fn wt_conn_err(e: &wtransport::error::ConnectionError) -> TransportError {
|
||||
use wtransport::error::ConnectionError as E;
|
||||
match e {
|
||||
E::ApplicationClosed(c) => TransportError::Closed {
|
||||
// The peer closed the session (WT_CLOSE_SESSION, or a FIN on the CONNECT stream,
|
||||
// which reads as code 0): the code is the MOQT session's.
|
||||
E::SessionClosed(c) => TransportError::Closed {
|
||||
code: c.code().into_inner(),
|
||||
reason: String::from_utf8_lossy(c.reason()).into_owned(),
|
||||
by_peer: true,
|
||||
},
|
||||
// The connection closed with the session still open: an HTTP/3 error code, which
|
||||
// says nothing of MOQT's.
|
||||
E::ApplicationClosed(c) => TransportError::Closed {
|
||||
code: u64::MAX,
|
||||
reason: format!(
|
||||
"the connection closed without closing the session (HTTP/3 error {:#x}){}{}",
|
||||
c.code().into_inner(),
|
||||
if c.reason().is_empty() { "" } else { ": " },
|
||||
String::from_utf8_lossy(c.reason())
|
||||
),
|
||||
by_peer: true,
|
||||
},
|
||||
E::LocallyClosed => TransportError::Closed { code: 0, reason: "closed by this endpoint".into(), by_peer: false },
|
||||
other => TransportError::Closed { code: u64::MAX, reason: other.to_string(), by_peer: true },
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WtSend(wtransport::SendStream);
|
||||
pub struct WtRecv(wtransport::RecvStream);
|
||||
pub struct WtSend(Option<wtransport::SendStream>, Arc<Closing>);
|
||||
pub struct WtRecv(Option<wtransport::RecvStream>, Arc<Closing>);
|
||||
|
||||
impl WtSend {
|
||||
fn new(s: wtransport::SendStream, closing: &Arc<Closing>) -> Self {
|
||||
WtSend(Some(s), closing.clone())
|
||||
}
|
||||
fn quic(&mut self) -> &mut quinn::SendStream {
|
||||
self.0.as_mut().expect("held until dropped").quic_stream_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl WtRecv {
|
||||
fn new(r: wtransport::RecvStream, closing: &Arc<Closing>) -> Self {
|
||||
WtRecv(Some(r), closing.clone())
|
||||
}
|
||||
fn quic(&mut self) -> &mut quinn::RecvStream {
|
||||
self.0.as_mut().expect("held until dropped").quic_stream_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WtSend {
|
||||
fn drop(&mut self) {
|
||||
if let Some(s) = self.0.take() {
|
||||
self.1.drop_or_hold(Box::new(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WtRecv {
|
||||
fn drop(&mut self) {
|
||||
if let Some(r) = self.0.take() {
|
||||
self.1.drop_or_hold(Box::new(r));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SendStream for WtSend {
|
||||
async fn write_all(&mut self, buf: &[u8]) -> TResult<()> {
|
||||
match self.0.quic_stream_mut().write_all(buf).await {
|
||||
match self.quic().write_all(buf).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(quinn::WriteError::Stopped(code)) => Err(TransportError::Stopped(h3_to_moqt(code.into_inner()))),
|
||||
Err(quinn::WriteError::ConnectionLost(c)) => Err(conn_err(&c)),
|
||||
|
|
@ -109,16 +195,16 @@ impl SendStream for WtSend {
|
|||
}
|
||||
}
|
||||
fn finish(&mut self) -> TResult<()> {
|
||||
self.0.quic_stream_mut().finish().map_err(TransportError::other)
|
||||
self.quic().finish().map_err(TransportError::other)
|
||||
}
|
||||
fn reset(&mut self, code: u64) {
|
||||
let _ = self.0.quic_stream_mut().reset(moqt_code_to_h3(code));
|
||||
let _ = self.quic().reset(moqt_code_to_h3(code));
|
||||
}
|
||||
fn set_priority(&mut self, priority: i32) {
|
||||
self.0.set_priority(priority);
|
||||
let _ = self.quic().set_priority(priority);
|
||||
}
|
||||
async fn delivered(&mut self) -> TResult<Option<u64>> {
|
||||
match self.0.quic_stream_mut().stopped().await {
|
||||
match self.quic().stopped().await {
|
||||
Ok(None) => Ok(None),
|
||||
Ok(Some(code)) => Ok(Some(h3_to_moqt(code.into_inner()))),
|
||||
Err(quinn::StoppedError::ConnectionLost(c)) => Err(conn_err(&c)),
|
||||
|
|
@ -130,7 +216,7 @@ impl SendStream for WtSend {
|
|||
#[async_trait]
|
||||
impl RecvStream for WtRecv {
|
||||
async fn read_chunk(&mut self) -> TResult<Option<Bytes>> {
|
||||
match self.0.quic_stream_mut().read_chunk(READ_CHUNK, true).await {
|
||||
match self.quic().read_chunk(READ_CHUNK, true).await {
|
||||
Ok(Some(c)) => Ok(Some(c.bytes)),
|
||||
Ok(None) => Ok(None),
|
||||
Err(quinn::ReadError::Reset(code)) => Err(TransportError::Reset(h3_to_moqt(code.into_inner()))),
|
||||
|
|
@ -139,7 +225,7 @@ impl RecvStream for WtRecv {
|
|||
}
|
||||
}
|
||||
fn stop(&mut self, code: u64) {
|
||||
let _ = self.0.quic_stream_mut().stop(moqt_code_to_h3(code));
|
||||
let _ = self.quic().stop(moqt_code_to_h3(code));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,20 +243,20 @@ impl Connection for WtConnection {
|
|||
async fn open_uni(&self) -> TResult<BoxSend> {
|
||||
let opening = self.conn.open_uni().await.map_err(|e| wt_conn_err(&e))?;
|
||||
let s = opening.await.map_err(TransportError::other)?;
|
||||
Ok(Box::new(WtSend(s)))
|
||||
Ok(Box::new(WtSend::new(s, &self.closing)))
|
||||
}
|
||||
async fn open_bi(&self) -> TResult<(BoxSend, BoxRecv)> {
|
||||
let opening = self.conn.open_bi().await.map_err(|e| wt_conn_err(&e))?;
|
||||
let (s, r) = opening.await.map_err(TransportError::other)?;
|
||||
Ok((Box::new(WtSend(s)), Box::new(WtRecv(r))))
|
||||
Ok((Box::new(WtSend::new(s, &self.closing)), Box::new(WtRecv::new(r, &self.closing))))
|
||||
}
|
||||
async fn accept_uni(&self) -> TResult<BoxRecv> {
|
||||
let r = self.conn.accept_uni().await.map_err(|e| wt_conn_err(&e))?;
|
||||
Ok(Box::new(WtRecv(r)))
|
||||
Ok(Box::new(WtRecv::new(r, &self.closing)))
|
||||
}
|
||||
async fn accept_bi(&self) -> TResult<(BoxSend, BoxRecv)> {
|
||||
let (s, r) = self.conn.accept_bi().await.map_err(|e| wt_conn_err(&e))?;
|
||||
Ok((Box::new(WtSend(s)), Box::new(WtRecv(r))))
|
||||
Ok((Box::new(WtSend::new(s, &self.closing)), Box::new(WtRecv::new(r, &self.closing))))
|
||||
}
|
||||
fn send_datagram(&self, payload: Bytes) -> TResult<()> {
|
||||
self.conn.send_datagram(&payload).map_err(TransportError::other)
|
||||
|
|
@ -179,9 +265,23 @@ impl Connection for WtConnection {
|
|||
let d = self.conn.receive_datagram().await.map_err(|e| wt_conn_err(&e))?;
|
||||
Ok(d.payload())
|
||||
}
|
||||
/// WT_CLOSE_SESSION with the MOQT code as the session's application error code (a code
|
||||
/// past 32 bits as `u32::MAX`) and the reason, then the connection's close; the streams
|
||||
/// dropped from here are held until the connection has closed (module docs).
|
||||
fn close(&self, code: u64, reason: &[u8]) {
|
||||
let code = VarInt::try_from_u64(code).unwrap_or(VarInt::MAX);
|
||||
self.conn.close(code, reason);
|
||||
if let Ok(rt) = tokio::runtime::Handle::try_current()
|
||||
&& !self.closing.holding.swap(true, Ordering::AcqRel)
|
||||
{
|
||||
let quic = self.conn.quic_connection().clone();
|
||||
let closing = self.closing.clone();
|
||||
rt.spawn(async move {
|
||||
let _ = tokio::time::timeout(HOLD, quic.closed()).await;
|
||||
closing.holding.store(false, Ordering::Release);
|
||||
closing.held.lock().unwrap().clear();
|
||||
});
|
||||
}
|
||||
let code = u32::try_from(code).unwrap_or(u32::MAX);
|
||||
self.conn.close_session(code, &String::from_utf8_lossy(reason));
|
||||
}
|
||||
async fn closed(&self) -> TransportError {
|
||||
wt_conn_err(&self.conn.closed().await)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,119 @@
|
|||
//! A session over WebTransport is closed by closing the WebTransport session
|
||||
//! (`draft-ietf-moq-transport-22` §6.6): the MOQT session termination code and the reason
|
||||
//! ride a WT_CLOSE_SESSION capsule on the CONNECT stream (`draft-ietf-webtrans-http3-16`
|
||||
//! §6), and the other end reads both exactly, as it does over the WebSocket binding. A QUIC
|
||||
//! connection close alone carries an HTTP/3 error code, which no end reads as MOQT's.
|
||||
//!
|
||||
//! The relay closes a session four ways here: refusing a room grant presented without its
|
||||
//! proof, after SETUP; refusing a stream that breaks a rule before SETUP, which it stops only
|
||||
//! after the session's close; refusing a session past its session count, before any MOQT
|
||||
//! message; and ending an established session at its time limit. And a client closes one,
|
||||
//! whose code the relay's signed summary records as the peer's.
|
||||
|
||||
mod common;
|
||||
|
||||
use common::*;
|
||||
use ed25519_dalek::SigningKey;
|
||||
use joule_moq::draft22::{SessionError, Token};
|
||||
use joule_moq_relay::client::ClientOptions;
|
||||
use joule_moq_relay::config::Grant;
|
||||
use joule_moq_relay::transport::{webtransport, Connection, TransportError};
|
||||
use joule_moq_relay::RelayServer;
|
||||
use std::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
|
||||
/// A record shaped as a room grant (`WRG1`, version 1, a room, the principal), which is
|
||||
/// all the relay reads of it: its rule names the record by its SHA-256.
|
||||
fn grant_record(room: u8, principal: &SigningKey) -> Vec<u8> {
|
||||
let mut g = b"WRG1\x01".to_vec();
|
||||
g.extend_from_slice(&[room; 32]);
|
||||
g.extend_from_slice(&principal.verifying_key().to_bytes());
|
||||
g.extend_from_slice(&[0x5a; 160]);
|
||||
g.extend_from_slice(&[0x77; 64]);
|
||||
g
|
||||
}
|
||||
|
||||
async fn closed(conn: &dyn Connection, what: &str) -> TransportError {
|
||||
timeout(STEP, conn.closed()).await.unwrap_or_else(|_| panic!("{what}: not closed in time"))
|
||||
}
|
||||
|
||||
fn by_relay(code: SessionError, reason: &str) -> TransportError {
|
||||
TransportError::Closed { code: code.0, reason: reason.into(), by_peer: true }
|
||||
}
|
||||
|
||||
/// A WebTransport session to `server` with no MOQT on it yet.
|
||||
async fn session(server: &RelayServer) -> webtransport::WtConnection {
|
||||
let url = format!("https://127.0.0.1:{}{}", server.quic_addr.unwrap().port(), server.relay().config().path);
|
||||
timeout(STEP, webtransport::connect(&url, Some(server.identity.leaf_sha256), loopback()))
|
||||
.await
|
||||
.expect("in time")
|
||||
.expect("a WebTransport session")
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn a_session_over_webtransport_closes_with_its_code_and_reason() {
|
||||
timeout(Duration::from_secs(120), run()).await.expect("the test finishes in time");
|
||||
}
|
||||
|
||||
async fn run() {
|
||||
// A room grant presented as a bare token: the relay refuses the session after SETUP,
|
||||
// UNAUTHORIZED (WAI live-rooms §10.5.2).
|
||||
let host = SigningKey::from_bytes(&[0x51; 32]);
|
||||
let record = grant_record(3, &host);
|
||||
let hash = hex::encode(joule_moq_relay::auth::sha256(&record));
|
||||
let rooms = relay(|c| {
|
||||
c.auth.required = true;
|
||||
c.auth.grants = vec![Grant { prefix: vec!["room".into()], token_sha256: hash, publish: true, subscribe: true, proof: true, unlimited: false, seek: false }];
|
||||
})
|
||||
.await;
|
||||
let bare = ClientOptions { tokens: vec![Token::UseValue { token_type: 0, value: record }], ..ClientOptions::default() };
|
||||
let (c, _) = connect(&rooms, Via::WebTransport, bare).await;
|
||||
assert_eq!(closed(c.conn.as_ref(), "a grant with no proof").await, by_relay(SessionError::UNAUTHORIZED, "live-rooms §10.5: pop_missing"));
|
||||
|
||||
// A stream that breaks a rule before SETUP: refused, PROTOCOL_VIOLATION. The relay ends
|
||||
// that stream only after the session's close, so the client reads the close and never
|
||||
// a STOP_SENDING ahead of it.
|
||||
let raw = session(&rooms).await;
|
||||
let mut s = timeout(STEP, raw.open_uni()).await.expect("in time").expect("a stream");
|
||||
s.write_all(&[0x3f; 4]).await.expect("written");
|
||||
match closed(&raw, "a stream that breaks a rule").await {
|
||||
TransportError::Closed { code, reason, by_peer: true } if code == SessionError::PROTOCOL_VIOLATION.0 => assert!(reason.contains("invalid flags"), "{reason}"),
|
||||
other => panic!("a stream that breaks a rule: {other:?}"),
|
||||
}
|
||||
match timeout(STEP, s.delivered()).await.expect("in time") {
|
||||
Err(TransportError::Closed { .. }) => {}
|
||||
other => panic!("the relay stopped the stream ahead of the session's close: {other:?}"),
|
||||
}
|
||||
|
||||
// A client closes its session with a code of its own: the relay reads it as the peer's,
|
||||
// and its summary records it; the client's own end reads its close as local.
|
||||
let (c, _) = connect(&rooms, Via::WebTransport, ClientOptions::default()).await;
|
||||
c.close(SessionError::PROTOCOL_VIOLATION.0, "a client's own close");
|
||||
assert_eq!(
|
||||
closed(c.conn.as_ref(), "the client's close").await,
|
||||
TransportError::Closed { code: 0, reason: "closed by this endpoint".into(), by_peer: false }
|
||||
);
|
||||
let recorded = until(STEP, || {
|
||||
rooms.relay().summaries().iter().any(|s| s.summary.transport == "webtransport" && s.summary.close_code == Some(SessionError::PROTOCOL_VIOLATION.0))
|
||||
})
|
||||
.await;
|
||||
let codes: Vec<_> = rooms.relay().summaries().iter().map(|s| (s.summary.transport.clone(), s.summary.close_code)).collect();
|
||||
assert!(recorded, "the relay records the client's code: {codes:?}");
|
||||
rooms.shutdown().await;
|
||||
|
||||
// A relay that holds one session at a time, each for three seconds at most.
|
||||
let limited = relay(|c| {
|
||||
c.limits.max_sessions = 1;
|
||||
c.limits.max_session_ms = 3_000;
|
||||
})
|
||||
.await;
|
||||
let (held, _) = connect(&limited, Via::WebTransport, ClientOptions::default()).await;
|
||||
assert!(until(STEP, || limited.relay().session_diagnostics().len() == 1).await, "the first session is held");
|
||||
// Past its session count: refused before any MOQT message, INTERNAL_ERROR.
|
||||
let refused = session(&limited).await;
|
||||
assert_eq!(closed(&refused, "a session past the count").await, by_relay(SessionError::INTERNAL_ERROR, "the relay is at its session limit"));
|
||||
// At its time limit, an established session is ended with NO_ERROR and the reason, which
|
||||
// a FIN on the CONNECT stream alone (code 0, no reason) would not carry.
|
||||
assert_eq!(closed(held.conn.as_ref(), "a session at its time limit").await, by_relay(SessionError::NO_ERROR, "the relay's session limit of 3 s"));
|
||||
limited.shutdown().await;
|
||||
}
|
||||
|
|
@ -2627,8 +2627,6 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
|||
[[package]]
|
||||
name = "wtransport"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4273ce3157a3262a68665f8d3f20a0ac0c5b8a69ffd67f05ae986832ebec036"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"pem",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ repository = "https://github.com/transaction-science/open-standards"
|
|||
# joule-code workspace.
|
||||
[workspace]
|
||||
|
||||
# The relay's wtransport, vendored with its WebTransport session close; the joule-code
|
||||
# workspace patches it the same way (vendor/wtransport/VENDORED.md).
|
||||
[patch.crates-io]
|
||||
wtransport = { path = "../../vendor/wtransport" }
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
|
|
|
|||
|
|
@ -301,13 +301,12 @@ async function scenarioRoom(run, transport) {
|
|||
fs.writeFileSync(path.join(dir, "receipts-sizes.json"), JSON.stringify(sv, null, 1));
|
||||
|
||||
// A grant presented as a bare token, with no proof of its key: the relay refuses the
|
||||
// session (live-rooms §10.5.2), UNAUTHORIZED. Over the WebSocket binding the page reads
|
||||
// the code (2) and the reason; over WebTransport the relay's transport closes the QUIC
|
||||
// connection rather than the WebTransport session, so the page sees the session lost,
|
||||
// and the relay's log gives the reason.
|
||||
// session (live-rooms §10.5.2), UNAUTHORIZED. The page reads the code (2) and the reason
|
||||
// over either transport: over WebTransport the relay closes the WebTransport session
|
||||
// with them (WT_CLOSE_SESSION, moq-transport-22 §6.6), as over the WebSocket binding.
|
||||
const bare = await alice.evaluate(() => window.roomTest.tryBareGrant());
|
||||
const refusals = relay.lines.filter((l) => l.includes("without a proof that passes"));
|
||||
const coded = bare.transport === "websocket" ? !!bare.closed && bare.closed.code === 2 : !!bare.closed;
|
||||
const coded = !!bare.closed && bare.closed.code === 2 && /pop_missing/.test(bare.closed.reason);
|
||||
check(`${label}: a grant presented without its proof is refused the session`, coded && refusals.some((l) => l.includes("pop_missing")),
|
||||
`${JSON.stringify(bare).slice(0, 200)}; relay: ${(refusals[0] || "").replace(/^.*INFO /, "").slice(0, 200)}`);
|
||||
check(`${label}: the relay refused that session and no other`, refusals.length === 1, `${refusals.length} refused`);
|
||||
|
|
|
|||
6
joule-code/joule-code-rs/vendor/wtransport/.cargo_vcs_info.json
vendored
Normal file
6
joule-code/joule-code-rs/vendor/wtransport/.cargo_vcs_info.json
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"git": {
|
||||
"sha1": "d022a6ad5f6260cb526e06d8ca42d09516ce6440"
|
||||
},
|
||||
"path_in_vcs": "wtransport"
|
||||
}
|
||||
1941
joule-code/joule-code-rs/vendor/wtransport/Cargo.lock
generated
vendored
Normal file
1941
joule-code/joule-code-rs/vendor/wtransport/Cargo.lock
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
177
joule-code/joule-code-rs/vendor/wtransport/Cargo.toml
vendored
Normal file
177
joule-code/joule-code-rs/vendor/wtransport/Cargo.toml
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.88"
|
||||
name = "wtransport"
|
||||
version = "0.7.2"
|
||||
authors = ["Biagio Festa"]
|
||||
build = false
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Implementation of the WebTransport (over HTTP3) protocol"
|
||||
readme = "README.md"
|
||||
keywords = ["webtransport"]
|
||||
categories = [
|
||||
"network-programming",
|
||||
"asynchronous",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/BiagioFesta/wtransport"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = [
|
||||
"--cfg",
|
||||
"docsrs",
|
||||
]
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"bytes::bytes::Bytes",
|
||||
"quinn",
|
||||
"quinn::connection::Connecting",
|
||||
"quinn::connection::Connection",
|
||||
"quinn::incoming::Incoming",
|
||||
"quinn::recv_stream::ReadError",
|
||||
"quinn::recv_stream::RecvStream",
|
||||
"quinn::send_stream::SendStream",
|
||||
"quinn::send_stream::WriteError",
|
||||
"quinn_proto::config::ClientConfig",
|
||||
"quinn_proto::config::EndpointConfig",
|
||||
"quinn_proto::config::ServerConfig",
|
||||
"quinn_proto::config::transport::TransportConfig",
|
||||
"quinn_proto::connection::ConnectionError",
|
||||
"rustls",
|
||||
"rustls::client::client_conn::ClientConfig",
|
||||
"rustls::server::server_conn::ServerConfig",
|
||||
"rustls::verify::ServerCertVerifier",
|
||||
"rustls::webpki::anchors::RootCertStore",
|
||||
"time",
|
||||
"time::Duration",
|
||||
"time::offset_date_time::OffsetDateTime",
|
||||
"tokio::io::async_read::AsyncRead",
|
||||
"tokio::io::async_write::AsyncWrite",
|
||||
"wtransport_proto",
|
||||
"wtransport_proto::WEBTRANSPORT_ALPN",
|
||||
"wtransport_proto::ids::SessionId",
|
||||
"wtransport_proto::ids::StreamId",
|
||||
"wtransport_proto::varint::VarInt",
|
||||
]
|
||||
|
||||
[features]
|
||||
aws-lc-rs = [
|
||||
"quinn/aws-lc-rs",
|
||||
"quinn/rustls-aws-lc-rs",
|
||||
"rustls/aws-lc-rs",
|
||||
"rcgen?/aws_lc_rs",
|
||||
]
|
||||
aws-lc-rs-fips = [
|
||||
"quinn/aws-lc-rs-fips",
|
||||
"quinn/rustls-aws-lc-rs-fips",
|
||||
"rustls/fips",
|
||||
"rcgen?/fips",
|
||||
]
|
||||
dangerous-configuration = []
|
||||
default = [
|
||||
"self-signed",
|
||||
"ring",
|
||||
]
|
||||
fips = ["aws-lc-rs-fips"]
|
||||
quinn = []
|
||||
quinn-log = ["quinn/log"]
|
||||
ring = [
|
||||
"quinn/ring",
|
||||
"quinn/rustls",
|
||||
"rustls/ring",
|
||||
"rcgen?/ring",
|
||||
]
|
||||
self-signed = ["dep:rcgen"]
|
||||
|
||||
[lib]
|
||||
name = "wtransport"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.bytes]
|
||||
version = "1.4.0"
|
||||
|
||||
[dependencies.pem]
|
||||
version = "3.0.4"
|
||||
|
||||
[dependencies.quinn]
|
||||
version = "0.11.6"
|
||||
features = ["runtime-tokio"]
|
||||
default-features = false
|
||||
|
||||
[dependencies.rcgen]
|
||||
version = "0.14.5"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.rustls]
|
||||
version = "0.23.23"
|
||||
default-features = false
|
||||
|
||||
[dependencies.rustls-native-certs]
|
||||
version = "0.8.0"
|
||||
|
||||
[dependencies.rustls-pki-types]
|
||||
version = "1.13.2"
|
||||
|
||||
[dependencies.sha2]
|
||||
version = "0.11.0"
|
||||
|
||||
[dependencies.socket2]
|
||||
version = "0.6.3"
|
||||
|
||||
[dependencies.thiserror]
|
||||
version = "2.0.3"
|
||||
|
||||
[dependencies.time]
|
||||
version = "0.3.21"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.28.1"
|
||||
features = [
|
||||
"macros",
|
||||
"fs",
|
||||
"io-util",
|
||||
"time",
|
||||
]
|
||||
default-features = false
|
||||
|
||||
[dependencies.tracing]
|
||||
version = "0.1.37"
|
||||
|
||||
[dependencies.url]
|
||||
version = "2.4.0"
|
||||
|
||||
[dependencies.wtransport-proto]
|
||||
version = "0.7.2"
|
||||
features = ["async"]
|
||||
|
||||
[dependencies.x509-parser]
|
||||
version = "0.18.1"
|
||||
|
||||
[lints.clippy]
|
||||
manual_is_variant_and = "warn"
|
||||
manual_let_else = "warn"
|
||||
unnecessary_cast = "deny"
|
||||
|
||||
[lints.rust]
|
||||
unfulfilled_lint_expectations = "deny"
|
||||
unit_bindings = "deny"
|
||||
unsafe_op_in_unsafe_fn = "deny"
|
||||
unused_qualifications = "warn"
|
||||
105
joule-code/joule-code-rs/vendor/wtransport/Cargo.toml.orig
generated
vendored
Normal file
105
joule-code/joule-code-rs/vendor/wtransport/Cargo.toml.orig
generated
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
[package]
|
||||
name = "wtransport"
|
||||
version = "0.7.2"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["Biagio Festa"]
|
||||
description = "Implementation of the WebTransport (over HTTP3) protocol"
|
||||
repository = "https://github.com/BiagioFesta/wtransport"
|
||||
keywords = ["webtransport"]
|
||||
categories = [ "network-programming", "asynchronous" ]
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
workspace = ".."
|
||||
rust-version = "1.88"
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
required-features = ["dangerous-configuration"]
|
||||
|
||||
[[example]]
|
||||
name = "full"
|
||||
required-features = ["self-signed"]
|
||||
|
||||
[[example]]
|
||||
name = "server"
|
||||
required-features = ["self-signed"]
|
||||
|
||||
[[example]]
|
||||
name = "gencert"
|
||||
required-features = ["self-signed"]
|
||||
|
||||
[dependencies]
|
||||
bytes = "1.4.0"
|
||||
pem = "3.0.4"
|
||||
quinn = { version = "0.11.6", default-features = false, features = ["runtime-tokio"] }
|
||||
rcgen = { version = "0.14.5", default-features = false, optional = true }
|
||||
rustls = { version = "0.23.23", default-features = false }
|
||||
rustls-native-certs = "0.8.0"
|
||||
rustls-pki-types = "1.13.2"
|
||||
sha2 = "0.11.0"
|
||||
socket2 = "0.6.3"
|
||||
thiserror = "2.0.3"
|
||||
time = "0.3.21"
|
||||
tokio = { version = "1.28.1", default-features = false, features = ["macros", "fs", "io-util"] }
|
||||
tracing = "0.1.37"
|
||||
url = "2.4.0"
|
||||
wtransport-proto = { version = "0.7.2", path = "../wtransport-proto", features = ["async"] }
|
||||
x509-parser = "0.18.1"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.71"
|
||||
axum = "0.7.1"
|
||||
hyper = "1.0.1"
|
||||
tokio = { version = "1.28.1", default-features = false, features = ["rt-multi-thread"] }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
|
||||
[features]
|
||||
default = ["self-signed", "ring"]
|
||||
aws-lc-rs = ["quinn/aws-lc-rs", "quinn/rustls-aws-lc-rs", "rustls/aws-lc-rs", "rcgen?/aws_lc_rs"]
|
||||
aws-lc-rs-fips = ["quinn/aws-lc-rs-fips", "quinn/rustls-aws-lc-rs-fips", "rustls/fips", "rcgen?/fips"]
|
||||
dangerous-configuration = []
|
||||
fips = ["aws-lc-rs-fips"]
|
||||
quinn = []
|
||||
quinn-log = ["quinn/log"]
|
||||
ring = ["quinn/ring", "quinn/rustls", "rustls/ring", "rcgen?/ring"]
|
||||
self-signed = ["dep:rcgen"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"bytes::bytes::Bytes",
|
||||
"quinn",
|
||||
"quinn::connection::Connecting",
|
||||
"quinn::connection::Connection",
|
||||
"quinn::incoming::Incoming",
|
||||
"quinn::recv_stream::ReadError",
|
||||
"quinn::recv_stream::RecvStream",
|
||||
"quinn::send_stream::SendStream",
|
||||
"quinn::send_stream::WriteError",
|
||||
"quinn_proto::config::ClientConfig",
|
||||
"quinn_proto::config::EndpointConfig",
|
||||
"quinn_proto::config::ServerConfig",
|
||||
"quinn_proto::config::transport::TransportConfig",
|
||||
"quinn_proto::connection::ConnectionError",
|
||||
"rustls",
|
||||
"rustls::client::client_conn::ClientConfig",
|
||||
"rustls::server::server_conn::ServerConfig",
|
||||
"rustls::verify::ServerCertVerifier",
|
||||
"rustls::webpki::anchors::RootCertStore",
|
||||
"time",
|
||||
"time::Duration",
|
||||
"time::offset_date_time::OffsetDateTime",
|
||||
"tokio::io::async_read::AsyncRead",
|
||||
"tokio::io::async_write::AsyncWrite",
|
||||
"wtransport_proto",
|
||||
"wtransport_proto::WEBTRANSPORT_ALPN",
|
||||
"wtransport_proto::ids::SessionId",
|
||||
"wtransport_proto::ids::StreamId",
|
||||
"wtransport_proto::varint::VarInt",
|
||||
]
|
||||
176
joule-code/joule-code-rs/vendor/wtransport/LICENSE-APACHE
vendored
Normal file
176
joule-code/joule-code-rs/vendor/wtransport/LICENSE-APACHE
vendored
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
21
joule-code/joule-code-rs/vendor/wtransport/LICENSE-MIT
vendored
Normal file
21
joule-code/joule-code-rs/vendor/wtransport/LICENSE-MIT
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Biagio Festa
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
139
joule-code/joule-code-rs/vendor/wtransport/README.md
vendored
Normal file
139
joule-code/joule-code-rs/vendor/wtransport/README.md
vendored
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/BiagioFesta/wtransport/master/imgs/logo.svg" alt="WTransport Logo" />
|
||||
</p>
|
||||
|
||||
[](https://docs.rs/wtransport/)
|
||||
[](https://crates.io/crates/wtransport)
|
||||
[](https://github.com/BiagioFesta/wtransport/actions/workflows/ci.yml)
|
||||
[](https://discord.gg/KPrrbWe5zg)
|
||||
|
||||
# WTransport
|
||||
[WebTransport](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/) protocol, pure-*rust*, *async*-friendly.
|
||||
|
||||
## Introduction
|
||||
|
||||
WebTransport is a new protocol being developed to enable *low-latency*, *bidirectional* communication between clients and servers over the web.
|
||||
It aims to address the limitations of existing protocols like *HTTP* and *WebSocket* by offering a more *efficient* and *flexible* transport layer.
|
||||
|
||||
### Benefits of WebTransport
|
||||
* 🚀 **Low latency**: WebTransport is designed to minimize latency, making it suitable for real-time applications such as gaming, video streaming, and collaborative editing.
|
||||
* 🔄 **Bidirectional communication**: WebTransport allows simultaneous data exchange between the client and server, enabling efficient back-and-forth communication without the need for multiple requests.
|
||||
* 🔀 **Multiplexing**: With WebTransport, multiple streams can be multiplexed over a single connection, reducing overhead and improving performance.
|
||||
* 🔒 **Security**: WebTransport benefits from the security features provided by the web platform, including transport encryption and same-origin policy.
|
||||
* 🌐 **Native Browser Support**: WebTransport is natively supported in modern web browsers, ensuring seamless integration and enhanced performance for web applications.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://docs.rs/wtransport/latest/wtransport/">Check Library Documentation</a>
|
||||
</p>
|
||||
|
||||
### Notes
|
||||
Please be aware that WebTransport is still a *draft* and not yet standardized.
|
||||
The *WTransport* library, while functional, is not considered completely production-ready.
|
||||
It should be used with caution and may undergo changes as the WebTransport specification evolves.
|
||||
|
||||
## Simple API
|
||||
<table>
|
||||
<tr>
|
||||
<th> Server </th>
|
||||
<th> Client </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```rust
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let config = ServerConfig::builder()
|
||||
.with_bind_default(4433)
|
||||
.with_identity(&identity)
|
||||
.build();
|
||||
|
||||
let connection = Endpoint::server(config)?
|
||||
.accept()
|
||||
.await // Awaits connection
|
||||
.await? // Awaits session request
|
||||
.accept() // Accepts request
|
||||
.await?; // Awaits ready session
|
||||
|
||||
let stream = connection.accept_bi().await?;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
```rust
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let config = ClientConfig::default();
|
||||
|
||||
let connection = Endpoint::client(config)?
|
||||
.connect("https://[::1]:4433")
|
||||
.await?;
|
||||
|
||||
let stream = connection.open_bi().await?.await?;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Browser Integration
|
||||
WebTransport [is supported](https://caniuse.com/mdn-api_webtransport) in modern browsers,
|
||||
enhancing the capabilities of web applications.
|
||||
|
||||
For instance, you can create a native *browser WebTransport* client connecting to a *Rust
|
||||
server* using the following JavaScript code:
|
||||
|
||||
```javascript
|
||||
// Create a WebTransport instance connecting to the Rust server
|
||||
let transport = new WebTransport('https://[::1]:4433');
|
||||
await transport.ready;
|
||||
|
||||
// Create a bidirectional stream
|
||||
let stream = await transport.createBidirectionalStream();
|
||||
|
||||
// Send data from the client to the server
|
||||
await stream.writable.getWriter().write(new TextEncoder().encode("hello"));
|
||||
|
||||
// Read data reply from the server
|
||||
let data = await stream.readable.getReader().read();
|
||||
console.log(data);
|
||||
```
|
||||
|
||||
Check out the [W3C WebTransport API documentation](https://w3c.github.io/webtransport/) for more details and to
|
||||
explore the full capabilities of WebTransport in the browser.
|
||||
|
||||
## Getting Started
|
||||
### Clone the Repository
|
||||
```bash
|
||||
git clone https://github.com/BiagioFesta/wtransport.git
|
||||
```
|
||||
```bash
|
||||
cd wtransport/
|
||||
```
|
||||
|
||||
### Run `Full` Example
|
||||
|
||||
The [`examples/full.rs`](wtransport/examples/full.rs) is a minimal but complete server example that demonstrates the usage of WebTransport.
|
||||
|
||||
You can run this example using [*Cargo*](https://rustup.rs/), Rust's package manager, with the following command:
|
||||
```bash
|
||||
cargo run --example full
|
||||
```
|
||||
|
||||
This example initiates an *echo* WebTransport server that can receive messages. It also includes an integrated HTTP server.
|
||||
|
||||
Open [a supported web browser](https://caniuse.com/mdn-api_webtransport) and navigate to the page http://127.0.0.1:8080.
|
||||
|
||||
## Examples
|
||||
* [Local Examples](https://github.com/BiagioFesta/wtransport/tree/master/wtransport/examples)
|
||||
|
||||
## Other Languages
|
||||
|
||||
WTransport has bindings for the following languages:
|
||||
|
||||
- Elixir: [wtransport-elixir](https://github.com/bugnano/wtransport-elixir)
|
||||
62
joule-code/joule-code-rs/vendor/wtransport/VENDORED.md
vendored
Normal file
62
joule-code/joule-code-rs/vendor/wtransport/VENDORED.md
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# wtransport 0.7.2, vendored
|
||||
|
||||
**Upstream.** The `wtransport` crate, version 0.7.2, exactly as published on crates.io: the
|
||||
`.crate` file's SHA-256 is `b4273ce3157a3262a68665f8d3f20a0ac0c5b8a69ffd67f05ae986832ebec036`,
|
||||
the checksum the joule-code lock held for it, and `.cargo_vcs_info.json` names the upstream
|
||||
commit it was packaged from (`d022a6ad5f6260cb526e06d8ca42d09516ce6440`, directory
|
||||
`wtransport`). `LICENSE-MIT` and `LICENSE-APACHE` are the upstream's, from that commit; the
|
||||
crate is MIT OR Apache-2.0, and these changes are offered under the same terms. The
|
||||
crate's companion, `wtransport-proto` 0.7.2, is not vendored: nothing in it changes.
|
||||
|
||||
**Where it is used.** `[patch.crates-io]` in `joule-code/joule-code-rs/Cargo.toml` and in
|
||||
`crates/joule-moq-room/Cargo.toml` (a workspace of its own that builds the relay). Its
|
||||
`Cargo.lock` and `Cargo.toml.orig` are the upstream's, kept with the source; cargo reads
|
||||
neither, and `scripts/check-lockfiles.sh` skips the lock.
|
||||
|
||||
## Why
|
||||
|
||||
A MOQT session over WebTransport is closed by closing the WebTransport session
|
||||
(`draft-ietf-moq-transport-22` §6.6, which calls the capsule by its earlier name,
|
||||
CLOSE_WEBTRANSPORT_SESSION): a WT_CLOSE_SESSION capsule on the session's CONNECT stream
|
||||
carries a 32-bit application error code and a UTF-8 reason of at most 1024 bytes, followed
|
||||
by a FIN (`draft-ietf-webtrans-http3-16` §6). The release reads that capsule but cannot
|
||||
send it: its CONNECT stream belongs to the driver's worker, and `Connection::close` closes
|
||||
the QUIC connection, whose code a browser's WebTransport API does not expose. A page whose
|
||||
session joule-moq-relay refused saw only that the session was lost.
|
||||
|
||||
## What changed
|
||||
|
||||
- `Connection::close_session(error_code: u32, reason: &str)`. It hands the driver's worker
|
||||
a request (`Driver::close_session`); the worker writes one HTTP/3 DATA frame holding the
|
||||
capsule (`close_session_capsule`, in `src/driver/streams/connect.rs`) on the CONNECT
|
||||
stream at the highest send priority, sends the FIN, and then reads that stream until the
|
||||
peer's FIN or reset, at most two seconds (`SESSION_CLOSE_LINGER`), before it closes the
|
||||
QUIC connection with H3_NO_ERROR: §6 asks an endpoint to wait for the peer to close its
|
||||
CONNECT stream first, so the connection's close cannot overtake the capsule. The reason
|
||||
is cut to 1024 bytes at a UTF-8 character boundary, as §6 requires of a sender that
|
||||
truncates.
|
||||
- After `close_session` this end opens no stream and sends no datagram (§6): `open_uni`,
|
||||
`open_bi` and `send_datagram` fail at once.
|
||||
- A close requested just as the session's last handle is dropped still goes out: the
|
||||
worker's loop can see the handles gone first, so `Worker::run` takes a pending request
|
||||
before it ends.
|
||||
- `ConnectionError::SessionClosed`, for a session the peer closed (the capsule's code and
|
||||
reason, or code 0 and no reason for a FIN alone, §6). The release reported that as
|
||||
`ApplicationClosed`, the variant it also uses for the QUIC connection's close, whose code
|
||||
under HTTP/3 is an HTTP/3 error code; the two could not be told apart.
|
||||
- `Connection::closed()` resolves with how the session ended, as the driver saw it, rather
|
||||
than with how the QUIC connection closed after: `SessionClosed` with the peer's code and
|
||||
reason, or `LocallyClosed` as soon as this end's capsule is queued. The release's
|
||||
`closed()` gave the QUIC close, so a peer's capsule was never reported there.
|
||||
- The tokio feature `time` is named, for the wait's bound (it was on already through quinn).
|
||||
- The upstream's examples and dev-dependencies are left out, so that the crate's unit tests,
|
||||
the upstream's and those of the capsule's layout, run from the joule-code workspace:
|
||||
`cargo test --locked -p wtransport --lib` (cargo tests a non-member crate only when it
|
||||
has no dev-dependencies). The CI job of joule-moq-relay runs them.
|
||||
|
||||
## What did not change
|
||||
|
||||
The streams of a session this end closes are not reset one by one with WT_SESSION_GONE, as
|
||||
§6 asks of an endpoint whose connection carries on; the driver does not track them. They end
|
||||
with the QUIC connection, at most two seconds later. joule-moq-relay serves one session per
|
||||
connection, and the session ends at the capsule for both ends.
|
||||
1287
joule-code/joule-code-rs/vendor/wtransport/src/config.rs
vendored
Normal file
1287
joule-code/joule-code-rs/vendor/wtransport/src/config.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
454
joule-code/joule-code-rs/vendor/wtransport/src/connection.rs
vendored
Normal file
454
joule-code/joule-code-rs/vendor/wtransport/src/connection.rs
vendored
Normal file
|
|
@ -0,0 +1,454 @@
|
|||
//! # WebTransport Connection
|
||||
//!
|
||||
//! [`Connection`] provides an essential building block for managing WebTransport
|
||||
//! connections. It allows you to initiate, accept, and control data *streams*, send and receive
|
||||
//! *datagrams*, monitor connection status, and interact with various aspects of your WebTransport
|
||||
//! communication.
|
||||
//!
|
||||
//! WebTransport exchanges data either via [*streams*](crate#streams) or [*datagrams*](crate#datagrams).
|
||||
//!
|
||||
//! ## Streams
|
||||
//! WebTransport streams provide a lightweight, ordered byte-stream abstraction.
|
||||
//!
|
||||
//! There are two fundamental types of streams:
|
||||
//! - *Unidirectional* streams carry data in a single direction, from the stream initiator to its peer.
|
||||
//! - *Bidirectional* streams allow for data to be sent in both directions.
|
||||
//!
|
||||
//! Both server and client endpoints have the capability to create an arbitrary number of streams to
|
||||
//! operate concurrently.
|
||||
//!
|
||||
//! Each stream can be independently cancelled by both side.
|
||||
//!
|
||||
//! ### Examples
|
||||
//! #### Open a stream
|
||||
//! ```no_run
|
||||
//! # use anyhow::Result;
|
||||
//! # async fn foo(connection: wtransport::Connection) -> Result<()> {
|
||||
//! use wtransport::Connection;
|
||||
//!
|
||||
//! // Open a bi-directional stream
|
||||
//! let (mut send_stream, mut recv_stream) = connection.open_bi().await?.await?;
|
||||
//!
|
||||
//! // Send data on the stream
|
||||
//! send_stream.write_all(b"Hello, wtransport!").await?;
|
||||
//!
|
||||
//! // Receive data from the stream
|
||||
//! let mut buffer = vec![0; 1024];
|
||||
//! let bytes_read = recv_stream.read(&mut buffer).await?;
|
||||
//!
|
||||
//! // Open an uni-directional stream (can only send data)
|
||||
//! let mut send_stream = connection.open_uni().await?.await?;
|
||||
//!
|
||||
//! // Send data on the stream
|
||||
//! send_stream.write_all(b"Hello, wtransport!").await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! #### Accept a stream
|
||||
//! ```no_run
|
||||
//! # use anyhow::Result;
|
||||
//! # async fn foo(connection: wtransport::Connection) -> Result<()> {
|
||||
//! use wtransport::Connection;
|
||||
//!
|
||||
//! // Await the peer opens a bi-directional stream
|
||||
//! let (mut send_stream, mut recv_stream) = connection.accept_bi().await?;
|
||||
//!
|
||||
//! // Can send and receive data on peer's stream
|
||||
//! send_stream.write_all(b"Hello, wtransport!").await?;
|
||||
//! # let mut buffer = vec![0; 1024];
|
||||
//! let bytes_read = recv_stream.read(&mut buffer).await?;
|
||||
//!
|
||||
//! // Await the peer opens an uni-directional stream (can only receive data)
|
||||
//! let mut recv_stream = connection.accept_uni().await?;
|
||||
//!
|
||||
//! // Receive data on the stream
|
||||
//! let bytes_read = recv_stream.read(&mut buffer).await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Datagrams
|
||||
//! WebTransport datagrams are similar to UDP datagrams but come with an
|
||||
//! added layer of security through *encryption* and *congestion control*.
|
||||
//! Datagrams can arrive out of order or might not arrive at all, offering
|
||||
//! flexibility in data exchange scenarios.
|
||||
//!
|
||||
//! Unlike streams, which operate as byte-stream abstractions, WebTransport
|
||||
//! datagrams act more like messages.
|
||||
//!
|
||||
//! ### Examples
|
||||
//! ```no_run
|
||||
//! # use anyhow::Result;
|
||||
//! # async fn foo(connection: wtransport::Connection) -> Result<()> {
|
||||
//! use wtransport::Connection;
|
||||
//!
|
||||
//! // Send datagram message
|
||||
//! connection.send_datagram(b"Hello, wtransport!")?;
|
||||
//!
|
||||
//! // Receive a datagram message
|
||||
//! let message = connection.receive_datagram().await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use crate::datagram::Datagram;
|
||||
use crate::driver::utils::varint_w2q;
|
||||
use crate::driver::Driver;
|
||||
use crate::error::ConnectionError;
|
||||
use crate::error::ExportKeyingMaterialError;
|
||||
use crate::error::SendDatagramError;
|
||||
use crate::stream::OpeningBiStream;
|
||||
use crate::stream::OpeningUniStream;
|
||||
use crate::stream::RecvStream;
|
||||
use crate::stream::SendStream;
|
||||
use crate::tls::Certificate;
|
||||
use crate::tls::CertificateChain;
|
||||
use crate::tls::HandshakeData;
|
||||
use crate::SessionId;
|
||||
use crate::VarInt;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// A WebTransport session connection.
|
||||
///
|
||||
/// For more details, see the [module documentation](crate::connection).
|
||||
///
|
||||
/// May be cloned to obtain another handle to the same connection.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Connection {
|
||||
quic_connection: quinn::Connection,
|
||||
driver: Arc<Driver>,
|
||||
session_id: SessionId,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub(crate) fn new(
|
||||
quic_connection: quinn::Connection,
|
||||
driver: Driver,
|
||||
session_id: SessionId,
|
||||
) -> Self {
|
||||
Self {
|
||||
quic_connection,
|
||||
driver: Arc::new(driver),
|
||||
session_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Asynchronously accepts a unidirectional stream.
|
||||
///
|
||||
/// This method is used to accept incoming unidirectional streams that have been initiated
|
||||
/// by the remote peer.
|
||||
/// It waits for the next unidirectional stream to be available, then wraps it in a
|
||||
/// [`RecvStream`] that can be used to read data from the stream.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe.
|
||||
pub async fn accept_uni(&self) -> Result<RecvStream, ConnectionError> {
|
||||
let stream = self
|
||||
.driver
|
||||
.accept_uni(self.session_id)
|
||||
.await
|
||||
.map_err(|driver_error| {
|
||||
ConnectionError::with_driver_error(driver_error, &self.quic_connection)
|
||||
})?
|
||||
.into_stream();
|
||||
|
||||
Ok(RecvStream::new(stream))
|
||||
}
|
||||
|
||||
/// Asynchronously accepts a bidirectional stream.
|
||||
///
|
||||
/// This method is used to accept incoming bidirectional streams that have been initiated
|
||||
/// by the remote peer.
|
||||
/// It waits for the next bidirectional stream to be available, then wraps it in a
|
||||
/// tuple containing a [`SendStream`] for sending data and a [`RecvStream`] for receiving
|
||||
/// data on the stream.
|
||||
///
|
||||
/// # Cancel safety
|
||||
///
|
||||
/// This method is cancel safe.
|
||||
pub async fn accept_bi(&self) -> Result<(SendStream, RecvStream), ConnectionError> {
|
||||
let stream = self
|
||||
.driver
|
||||
.accept_bi(self.session_id)
|
||||
.await
|
||||
.map_err(|driver_error| {
|
||||
ConnectionError::with_driver_error(driver_error, &self.quic_connection)
|
||||
})?
|
||||
.into_stream();
|
||||
|
||||
Ok((SendStream::new(stream.0), RecvStream::new(stream.1)))
|
||||
}
|
||||
|
||||
/// Asynchronously opens a new unidirectional stream.
|
||||
///
|
||||
/// This method is used to initiate the opening of a new unidirectional stream.
|
||||
///
|
||||
/// # Asynchronous Behavior
|
||||
///
|
||||
/// This method is asynchronous and involves two `await` points:
|
||||
///
|
||||
/// 1. The first `await` occurs during the initial phase of opening the stream, which may involve awaiting
|
||||
/// the flow controller. This wait is necessary to ensure proper resource allocation and flow control.
|
||||
/// It is safe to cancel this `await` point if needed.
|
||||
///
|
||||
/// 2. The second `await` is internal to the returned [`OpeningUniStream`] object when it is used to initialize
|
||||
/// the WebTransport stream. Cancelling this latter future before it completes may result in the stream
|
||||
/// being closed during initialization.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use wtransport::Connection;
|
||||
/// # use anyhow::Result;
|
||||
/// # async fn run(connection: Connection) -> Result<()> {
|
||||
/// let send_stream = connection.open_uni().await?.await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn open_uni(&self) -> Result<OpeningUniStream, ConnectionError> {
|
||||
self.driver
|
||||
.open_uni(self.session_id)
|
||||
.await
|
||||
.map_err(|driver_error| {
|
||||
ConnectionError::with_driver_error(driver_error, &self.quic_connection)
|
||||
})
|
||||
}
|
||||
|
||||
/// Asynchronously opens a new bidirectional stream.
|
||||
///
|
||||
/// This method is used to initiate the opening of a new bidirectional stream.
|
||||
///
|
||||
/// # Asynchronous Behavior
|
||||
///
|
||||
/// This method is asynchronous and involves two `await` points:
|
||||
///
|
||||
/// 1. The first `await` occurs during the initial phase of opening the stream, which may involve awaiting
|
||||
/// the flow controller. This wait is necessary to ensure proper resource allocation and flow control.
|
||||
/// It is safe to cancel this `await` point if needed.
|
||||
///
|
||||
/// 2. The second `await` is internal to the returned [`OpeningBiStream`] object when it is used to initialize
|
||||
/// the WebTransport stream. Cancelling this latter future before it completes may result in the stream
|
||||
/// being closed during initialization.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use wtransport::Connection;
|
||||
/// # use anyhow::Result;
|
||||
/// # async fn run(connection: Connection) -> Result<()> {
|
||||
/// let (send_stream, recv_stream) = connection.open_bi().await?.await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn open_bi(&self) -> Result<OpeningBiStream, ConnectionError> {
|
||||
self.driver
|
||||
.open_bi(self.session_id)
|
||||
.await
|
||||
.map_err(|driver_error| {
|
||||
ConnectionError::with_driver_error(driver_error, &self.quic_connection)
|
||||
})
|
||||
}
|
||||
|
||||
/// Asynchronously receives an application datagram from the remote peer.
|
||||
///
|
||||
/// This method is used to receive an application datagram sent by the remote
|
||||
/// peer over the connection.
|
||||
/// It waits for a datagram to become available and returns the received [`Datagram`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use wtransport::Connection;
|
||||
/// # use anyhow::Result;
|
||||
/// # async fn run(connection: Connection) -> Result<()> {
|
||||
/// let datagram = connection.receive_datagram().await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn receive_datagram(&self) -> Result<Datagram, ConnectionError> {
|
||||
self.driver
|
||||
.receive_datagram(self.session_id)
|
||||
.await
|
||||
.map_err(|driver_error| {
|
||||
ConnectionError::with_driver_error(driver_error, &self.quic_connection)
|
||||
})
|
||||
}
|
||||
|
||||
/// Sends an application datagram to the remote peer.
|
||||
///
|
||||
/// This method is used to send an application datagram to the remote peer
|
||||
/// over the connection.
|
||||
/// The datagram payload is provided as a reference to a slice of bytes.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use wtransport::Connection;
|
||||
/// # use anyhow::Result;
|
||||
/// # async fn run(connection: Connection) -> Result<()> {
|
||||
/// connection.send_datagram(b"Hello, wtransport!")?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn send_datagram<D>(&self, payload: D) -> Result<(), SendDatagramError>
|
||||
where
|
||||
D: AsRef<[u8]>,
|
||||
{
|
||||
self.driver.send_datagram(self.session_id, payload.as_ref())
|
||||
}
|
||||
|
||||
/// Closes the connection immediately.
|
||||
///
|
||||
/// This closes the QUIC connection: the peer learns no more than that the session is
|
||||
/// gone. To close the session with an application error code and reason the peer can
|
||||
/// read, use [`close_session`](Self::close_session).
|
||||
pub fn close(&self, error_code: VarInt, reason: &[u8]) {
|
||||
self.quic_connection.close(varint_w2q(error_code), reason);
|
||||
}
|
||||
|
||||
/// Closes the WebTransport session with a 32-bit application error code and a reason
|
||||
/// (draft-ietf-webtrans-http3-16 §6), then the connection, which carries this session
|
||||
/// alone.
|
||||
///
|
||||
/// A WT_CLOSE_SESSION capsule carrying `error_code` and `reason` (cut to 1024 bytes at
|
||||
/// a character boundary) goes out on the session's CONNECT stream, with a FIN, ahead of
|
||||
/// anything else queued. The session is over from here: this end opens no stream and
|
||||
/// sends no datagram, and [`closed`](Self::closed) resolves with
|
||||
/// [`ConnectionError::LocallyClosed`]. The connection is closed with H3_NO_ERROR once
|
||||
/// the peer has closed its side of the CONNECT stream, as it must on reading the
|
||||
/// capsule, or after two seconds, so that the connection's close cannot overtake the
|
||||
/// capsule. The streams of the session end with the connection.
|
||||
pub fn close_session(&self, error_code: u32, reason: &str) {
|
||||
self.driver.close_session(error_code, reason);
|
||||
}
|
||||
|
||||
/// Waits for the session to be closed for any reason.
|
||||
///
|
||||
/// A session the peer closed resolves with [`ConnectionError::SessionClosed`], its code
|
||||
/// and reason, rather than with how the connection closed after it.
|
||||
pub async fn closed(&self) -> ConnectionError {
|
||||
match self.driver.closed().await {
|
||||
Some(driver_error) => {
|
||||
ConnectionError::with_driver_error(driver_error, &self.quic_connection)
|
||||
}
|
||||
None => self.quic_connection.closed().await.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the WebTransport session identifier.
|
||||
#[inline(always)]
|
||||
pub fn session_id(&self) -> SessionId {
|
||||
self.session_id
|
||||
}
|
||||
|
||||
/// Returns the peer's UDP address.
|
||||
///
|
||||
/// **Note**: as QUIC supports migration, remote address may change
|
||||
/// during connection. Furthermore, when IPv6 support is enabled, IPv4
|
||||
/// addresses may be mapped to IPv6.
|
||||
#[inline(always)]
|
||||
pub fn remote_address(&self) -> SocketAddr {
|
||||
self.quic_connection.remote_address()
|
||||
}
|
||||
|
||||
/// A stable identifier for this connection.
|
||||
///
|
||||
/// Peer addresses and connection IDs can change, but this value will remain
|
||||
/// fixed for the lifetime of the connection.
|
||||
#[inline(always)]
|
||||
pub fn stable_id(&self) -> usize {
|
||||
self.quic_connection.stable_id()
|
||||
}
|
||||
|
||||
/// Computes the maximum size of datagrams that may be passed to
|
||||
/// [`send_datagram`](Self::send_datagram).
|
||||
///
|
||||
/// Returns `None` if datagrams are unsupported by the peer or disabled locally.
|
||||
///
|
||||
/// This may change over the lifetime of a connection according to variation in the path MTU
|
||||
/// estimate. The peer can also enforce an arbitrarily small fixed limit, but if the peer's
|
||||
/// limit is large this is guaranteed to be a little over a kilobyte at minimum.
|
||||
///
|
||||
/// Not necessarily the maximum size of received datagrams.
|
||||
#[inline(always)]
|
||||
pub fn max_datagram_size(&self) -> Option<usize> {
|
||||
self.quic_connection
|
||||
.max_datagram_size()
|
||||
.map(|quic_max_size| quic_max_size - Datagram::header_size(self.session_id))
|
||||
}
|
||||
|
||||
/// Current best estimate of this connection's latency (round-trip-time).
|
||||
#[inline(always)]
|
||||
pub fn rtt(&self) -> Duration {
|
||||
self.quic_connection.rtt()
|
||||
}
|
||||
|
||||
/// Derive keying material from this connection's TLS session secrets.
|
||||
///
|
||||
/// When both peers call this method with the same `label` and `context`
|
||||
/// arguments and `output` buffers of equal length, they will get the
|
||||
/// same sequence of bytes in `output`. These bytes are cryptographically
|
||||
/// strong and pseudorandom, and are suitable for use as keying material.
|
||||
///
|
||||
/// See [RFC5705](https://tools.ietf.org/html/rfc5705) for more information.
|
||||
pub fn export_keying_material(
|
||||
&self,
|
||||
output: &mut [u8],
|
||||
label: &[u8],
|
||||
context: &[u8],
|
||||
) -> Result<(), ExportKeyingMaterialError> {
|
||||
self.quic_connection
|
||||
.export_keying_material(output, label, context)
|
||||
.map_err(|_: quinn::crypto::ExportKeyingMaterialError| ExportKeyingMaterialError)
|
||||
}
|
||||
|
||||
/// Returns the peer's identity as a certificate chain if available.
|
||||
///
|
||||
/// This function returns an `Option` containing a [`CertificateChain`]. If the peer's identity
|
||||
/// is available, it is converted into a `CertificateChain` and returned. If the peer's identity
|
||||
/// is not available, `None` is returned.
|
||||
pub fn peer_identity(&self) -> Option<CertificateChain> {
|
||||
self.quic_connection.peer_identity().map(|any| {
|
||||
any.downcast::<Vec<rustls_pki_types::CertificateDer<'static>>>()
|
||||
.expect("rustls certificate vector")
|
||||
.into_iter()
|
||||
.map(Certificate::from_rustls_pki)
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieves handshake data associated with the connection.
|
||||
pub fn handshake_data(&self) -> HandshakeData {
|
||||
let hd = self
|
||||
.quic_connection
|
||||
.handshake_data()
|
||||
.expect("fully established connection")
|
||||
.downcast::<quinn::crypto::rustls::HandshakeData>()
|
||||
.expect("valid downcast");
|
||||
|
||||
HandshakeData {
|
||||
alpn: hd.protocol,
|
||||
server_name: hd.server_name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner QUIC connection.
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
#[inline(always)]
|
||||
pub fn quic_connection(&self) -> &quinn::Connection {
|
||||
&self.quic_connection
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner QUIC connection.
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
#[inline(always)]
|
||||
pub fn quic_connection_mut(&mut self) -> &mut quinn::Connection {
|
||||
&mut self.quic_connection
|
||||
}
|
||||
}
|
||||
76
joule-code/joule-code-rs/vendor/wtransport/src/datagram.rs
vendored
Normal file
76
joule-code/joule-code-rs/vendor/wtransport/src/datagram.rs
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use crate::SessionId;
|
||||
use bytes::Bytes;
|
||||
use std::ops::Deref;
|
||||
use wtransport_proto::datagram::Datagram as H3Datagram;
|
||||
use wtransport_proto::error::ErrorCode;
|
||||
use wtransport_proto::ids::QStreamId;
|
||||
|
||||
/// An application Datagram.
|
||||
#[derive(Debug)]
|
||||
pub struct Datagram {
|
||||
quic_dgram: Bytes,
|
||||
payload_offset: usize,
|
||||
session_id: SessionId,
|
||||
}
|
||||
|
||||
impl Datagram {
|
||||
/// Returns the datagram payload.
|
||||
#[inline(always)]
|
||||
pub fn payload(&self) -> Bytes {
|
||||
self.quic_dgram.slice(self.payload_offset..)
|
||||
}
|
||||
|
||||
pub(crate) fn read(quic_dgram: Bytes) -> Result<Self, ErrorCode> {
|
||||
let h3dgram = H3Datagram::read(&quic_dgram)?;
|
||||
let payload_offset = quic_dgram.len() - h3dgram.payload().len();
|
||||
let session_id = h3dgram.qstream_id().into_session_id();
|
||||
|
||||
Ok(Self {
|
||||
quic_dgram,
|
||||
payload_offset,
|
||||
session_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn write(session_id: SessionId, payload: &[u8]) -> Self {
|
||||
let h3dgram = H3Datagram::new(QStreamId::from_session_id(session_id), payload);
|
||||
|
||||
let mut buffer = vec![0; h3dgram.write_size()].into_boxed_slice();
|
||||
h3dgram.write(&mut buffer).expect("Preallocated capacity");
|
||||
|
||||
let quic_dgram = Bytes::from(buffer);
|
||||
|
||||
let payload_offset = quic_dgram.len() - payload.len();
|
||||
|
||||
Self {
|
||||
quic_dgram,
|
||||
payload_offset,
|
||||
session_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn header_size(session_id: SessionId) -> usize {
|
||||
H3Datagram::header_size(QStreamId::from_session_id(session_id))
|
||||
}
|
||||
|
||||
/// Returns the associated [`SessionId`].
|
||||
#[inline(always)]
|
||||
pub fn session_id(&self) -> SessionId {
|
||||
self.session_id
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn into_quic_bytes(self) -> Bytes {
|
||||
self.quic_dgram
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Datagram {
|
||||
type Target = [u8];
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.quic_dgram[self.payload_offset..]
|
||||
}
|
||||
}
|
||||
763
joule-code/joule-code-rs/vendor/wtransport/src/driver/mod.rs
vendored
Normal file
763
joule-code/joule-code-rs/vendor/wtransport/src/driver/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,763 @@
|
|||
use crate::datagram::Datagram;
|
||||
use crate::driver::streams::biremote::StreamBiRemoteH3;
|
||||
use crate::driver::streams::biremote::StreamBiRemoteWT;
|
||||
use crate::driver::streams::session::StreamSession;
|
||||
use crate::driver::streams::uniremote::StreamUniRemoteWT;
|
||||
use crate::driver::streams::Stream;
|
||||
use crate::driver::utils::bichannel;
|
||||
use crate::driver::utils::shared_result;
|
||||
use crate::driver::utils::SendError;
|
||||
use crate::driver::utils::SharedResultGet;
|
||||
use crate::driver::utils::SharedResultSet;
|
||||
use crate::error::ApplicationClose;
|
||||
use crate::error::SendDatagramError;
|
||||
use crate::stream::OpeningBiStream;
|
||||
use crate::stream::OpeningUniStream;
|
||||
use crate::SessionId;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::debug;
|
||||
use tracing::debug_span;
|
||||
use tracing::instrument;
|
||||
use tracing::trace;
|
||||
use tracing::Instrument;
|
||||
use utils::BiChannelEndpoint;
|
||||
use wtransport_proto::error::ErrorCode;
|
||||
use wtransport_proto::frame::Frame;
|
||||
use wtransport_proto::session::SessionRequest;
|
||||
use wtransport_proto::settings::Settings;
|
||||
|
||||
/// How long a session closed by this end waits for the peer to close its side of the
|
||||
/// CONNECT stream before the connection is closed (see [`Driver::close_session`]).
|
||||
const SESSION_CLOSE_LINGER: Duration = Duration::from_secs(2);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DriverError {
|
||||
Proto(ErrorCode),
|
||||
ApplicationClosed(ApplicationClose),
|
||||
/// This endpoint closed the session ([`Driver::close_session`]).
|
||||
LocallyClosed,
|
||||
NotConnected,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Driver {
|
||||
quic_connection: quinn::Connection,
|
||||
ready_settings: Mutex<mpsc::Receiver<Settings>>,
|
||||
ready_sessions: BiChannelEndpoint<StreamSession>,
|
||||
ready_uni_wt_streams: Mutex<mpsc::Receiver<StreamUniRemoteWT>>,
|
||||
ready_bi_wt_streams: Mutex<mpsc::Receiver<StreamBiRemoteWT>>,
|
||||
ready_datagrams: Mutex<mpsc::Receiver<Datagram>>,
|
||||
driver_result: SharedResultGet<DriverError>,
|
||||
close_requests: mpsc::UnboundedSender<(u32, String)>,
|
||||
session_closing: AtomicBool,
|
||||
}
|
||||
|
||||
impl Driver {
|
||||
pub fn init(quic_connection: quinn::Connection) -> Self {
|
||||
let ready_settings = mpsc::channel(1);
|
||||
let ready_sessions = bichannel(1);
|
||||
let ready_uni_wt_streams = mpsc::channel(4);
|
||||
let ready_bi_wt_streams = mpsc::channel(1);
|
||||
let ready_datagrams = mpsc::channel(1);
|
||||
let driver_result = shared_result();
|
||||
let close_requests = mpsc::unbounded_channel();
|
||||
|
||||
tokio::spawn(
|
||||
worker::Worker::new(
|
||||
quic_connection.clone(),
|
||||
ready_settings.0,
|
||||
ready_sessions.0,
|
||||
ready_uni_wt_streams.0,
|
||||
ready_bi_wt_streams.0,
|
||||
ready_datagrams.0,
|
||||
driver_result.0,
|
||||
close_requests.1,
|
||||
)
|
||||
.run()
|
||||
.instrument(debug_span!("Driver", quic_id = quic_connection.stable_id())),
|
||||
);
|
||||
|
||||
Self {
|
||||
quic_connection,
|
||||
ready_settings: Mutex::new(ready_settings.1),
|
||||
ready_sessions: ready_sessions.1,
|
||||
ready_uni_wt_streams: Mutex::new(ready_uni_wt_streams.1),
|
||||
ready_bi_wt_streams: Mutex::new(ready_bi_wt_streams.1),
|
||||
ready_datagrams: Mutex::new(ready_datagrams.1),
|
||||
driver_result: driver_result.1,
|
||||
close_requests: close_requests.0,
|
||||
session_closing: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the session from this end: the worker sends WT_CLOSE_SESSION with `error_code`
|
||||
/// and `reason` on the CONNECT stream, then closes the connection (see
|
||||
/// [`Connection::close_session`](crate::Connection::close_session)). From here this end
|
||||
/// opens no stream and sends no datagram (draft-ietf-webtrans-http3-16 §6).
|
||||
pub fn close_session(&self, error_code: u32, reason: &str) {
|
||||
self.session_closing.store(true, Ordering::Release);
|
||||
// An error means the worker has ended, and the session with it.
|
||||
let _ = self.close_requests.send((error_code, reason.to_owned()));
|
||||
}
|
||||
|
||||
/// Why the worker ended, once it has; `None` if it stopped without saying.
|
||||
pub async fn closed(&self) -> Option<DriverError> {
|
||||
self.driver_result.result().await
|
||||
}
|
||||
|
||||
fn closing(&self) -> bool {
|
||||
self.session_closing.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub async fn accept_settings(&self) -> Result<Settings, DriverError> {
|
||||
let mut lock = self.ready_settings.lock().await;
|
||||
|
||||
match lock.recv().await {
|
||||
Some(settings) => Ok(settings),
|
||||
None => Err(self.result().await),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn accept_session(&self) -> Result<StreamSession, DriverError> {
|
||||
match self.ready_sessions.recv().await {
|
||||
Some(session) => Ok(session),
|
||||
None => Err(self.result().await),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn open_session(
|
||||
&self,
|
||||
session_request: SessionRequest,
|
||||
) -> Result<StreamSession, DriverError> {
|
||||
let stream = Stream::open_bi(&self.quic_connection)
|
||||
.await
|
||||
.ok_or(DriverError::NotConnected)?
|
||||
.upgrade()
|
||||
.into_session(session_request);
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
pub async fn register_session(&self, stream_session: StreamSession) -> Result<(), DriverError> {
|
||||
match self.ready_sessions.send(stream_session).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(SendError) => Err(self.result().await),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn accept_uni(
|
||||
&self,
|
||||
session_id: SessionId,
|
||||
) -> Result<StreamUniRemoteWT, DriverError> {
|
||||
let mut lock = self.ready_uni_wt_streams.lock().await;
|
||||
|
||||
loop {
|
||||
let Some(stream) = lock.recv().await else {
|
||||
return Err(self.result().await);
|
||||
};
|
||||
|
||||
if stream.session_id() == session_id {
|
||||
return Ok(stream);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Discarding WT stream (stream_id: {}, session_id: {})",
|
||||
stream.id(),
|
||||
stream.session_id()
|
||||
);
|
||||
|
||||
stream
|
||||
.into_stream()
|
||||
.stop(ErrorCode::BufferedStreamRejected.to_code())
|
||||
.expect("Stream not already stopped");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn accept_bi(&self, session_id: SessionId) -> Result<StreamBiRemoteWT, DriverError> {
|
||||
let mut lock = self.ready_bi_wt_streams.lock().await;
|
||||
|
||||
loop {
|
||||
let Some(stream) = lock.recv().await else {
|
||||
return Err(self.result().await);
|
||||
};
|
||||
|
||||
if stream.session_id() == session_id {
|
||||
return Ok(stream);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Discarding WT stream (stream_id: {}, session_id: {})",
|
||||
stream.id(),
|
||||
stream.session_id()
|
||||
);
|
||||
|
||||
stream
|
||||
.into_stream()
|
||||
.1
|
||||
.stop(ErrorCode::BufferedStreamRejected.to_code())
|
||||
.expect("Stream not already stopped");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn receive_datagram(&self, session_id: SessionId) -> Result<Datagram, DriverError> {
|
||||
let mut lock = self.ready_datagrams.lock().await;
|
||||
|
||||
loop {
|
||||
let Some(datagram) = lock.recv().await else {
|
||||
return Err(self.result().await);
|
||||
};
|
||||
|
||||
if datagram.session_id() == session_id {
|
||||
return Ok(datagram);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Incoming datagram discarded (session_id: {})",
|
||||
datagram.session_id()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn open_uni(&self, session_id: SessionId) -> Result<OpeningUniStream, DriverError> {
|
||||
if self.closing() {
|
||||
return Err(DriverError::LocallyClosed);
|
||||
}
|
||||
|
||||
let quic_stream = Stream::open_uni(&self.quic_connection)
|
||||
.await
|
||||
.ok_or(DriverError::NotConnected)?;
|
||||
|
||||
Ok(OpeningUniStream::new(session_id, quic_stream))
|
||||
}
|
||||
|
||||
pub async fn open_bi(&self, session_id: SessionId) -> Result<OpeningBiStream, DriverError> {
|
||||
if self.closing() {
|
||||
return Err(DriverError::LocallyClosed);
|
||||
}
|
||||
|
||||
let quic_stream = Stream::open_bi(&self.quic_connection)
|
||||
.await
|
||||
.ok_or(DriverError::NotConnected)?;
|
||||
|
||||
Ok(OpeningBiStream::new(session_id, quic_stream))
|
||||
}
|
||||
|
||||
pub fn send_datagram(
|
||||
&self,
|
||||
session_id: SessionId,
|
||||
payload: &[u8],
|
||||
) -> Result<(), SendDatagramError> {
|
||||
if self.closing() {
|
||||
return Err(SendDatagramError::NotConnected);
|
||||
}
|
||||
|
||||
let quic_datagram = Datagram::write(session_id, payload).into_quic_bytes();
|
||||
|
||||
match self.quic_connection.send_datagram(quic_datagram) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(quinn::SendDatagramError::UnsupportedByPeer) => {
|
||||
Err(SendDatagramError::UnsupportedByPeer)
|
||||
}
|
||||
Err(quinn::SendDatagramError::Disabled) => {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
Err(quinn::SendDatagramError::TooLarge) => Err(SendDatagramError::TooLarge),
|
||||
Err(quinn::SendDatagramError::ConnectionLost(_)) => {
|
||||
Err(SendDatagramError::NotConnected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn result(&self) -> DriverError {
|
||||
match self.driver_result.result().await {
|
||||
Some(error) => error,
|
||||
None => panic!("Driver worker panic!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod worker {
|
||||
use super::*;
|
||||
use crate::driver::streams::qpack::RemoteQPackDecStream;
|
||||
use crate::driver::streams::qpack::RemoteQPackEncStream;
|
||||
use crate::driver::streams::settings::LocalSettingsStream;
|
||||
use crate::driver::streams::settings::RemoteSettingsStream;
|
||||
use crate::driver::streams::uniremote::StreamUniRemoteH3;
|
||||
use crate::driver::streams::ProtoReadError;
|
||||
use crate::driver::streams::ProtoWriteError;
|
||||
use crate::driver::utils::TrySendError;
|
||||
use streams::connect::ConnectStream;
|
||||
use utils::varint_w2q;
|
||||
use wtransport_proto::frame::FrameKind;
|
||||
use wtransport_proto::headers::Headers;
|
||||
use wtransport_proto::session::HeadersParseError;
|
||||
use wtransport_proto::stream_header::StreamHeader;
|
||||
use wtransport_proto::stream_header::StreamKind;
|
||||
|
||||
pub struct Worker {
|
||||
quic_connection: quinn::Connection,
|
||||
ready_settings: mpsc::Sender<Settings>,
|
||||
ready_sessions: BiChannelEndpoint<StreamSession>,
|
||||
ready_uni_wt_streams: mpsc::Sender<StreamUniRemoteWT>,
|
||||
ready_bi_wt_streams: mpsc::Sender<StreamBiRemoteWT>,
|
||||
ready_datagrams: mpsc::Sender<Datagram>,
|
||||
driver_result: SharedResultSet<DriverError>,
|
||||
local_settings_stream: LocalSettingsStream,
|
||||
remote_settings_stream: RemoteSettingsStream,
|
||||
remote_qpack_enc_stream: RemoteQPackEncStream,
|
||||
remote_qpack_dec_stream: RemoteQPackDecStream,
|
||||
connect_stream: ConnectStream,
|
||||
close_requests: mpsc::UnboundedReceiver<(u32, String)>,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
pub fn new(
|
||||
quic_connection: quinn::Connection,
|
||||
ready_settings: mpsc::Sender<Settings>,
|
||||
ready_sessions: BiChannelEndpoint<StreamSession>,
|
||||
ready_uni_wt_streams: mpsc::Sender<StreamUniRemoteWT>,
|
||||
ready_bi_wt_streams: mpsc::Sender<StreamBiRemoteWT>,
|
||||
ready_datagrams: mpsc::Sender<Datagram>,
|
||||
driver_result: SharedResultSet<DriverError>,
|
||||
close_requests: mpsc::UnboundedReceiver<(u32, String)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
quic_connection,
|
||||
ready_settings,
|
||||
ready_sessions,
|
||||
ready_uni_wt_streams,
|
||||
ready_bi_wt_streams,
|
||||
ready_datagrams,
|
||||
driver_result,
|
||||
local_settings_stream: LocalSettingsStream::empty(),
|
||||
remote_settings_stream: RemoteSettingsStream::empty(),
|
||||
remote_qpack_enc_stream: RemoteQPackEncStream::empty(),
|
||||
remote_qpack_dec_stream: RemoteQPackDecStream::empty(),
|
||||
connect_stream: ConnectStream::empty(),
|
||||
close_requests,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(mut self) {
|
||||
debug!("Started");
|
||||
|
||||
let mut error = self
|
||||
.run_impl()
|
||||
.await
|
||||
.expect_err("Worker must return an error");
|
||||
|
||||
// A close asked for as the session's last handle was dropped: the loop can see
|
||||
// the handles gone first. The close still goes out.
|
||||
if matches!(error, DriverError::NotConnected)
|
||||
&& self.quic_connection.close_reason().is_none()
|
||||
{
|
||||
if let Ok((error_code, reason)) = self.close_requests.try_recv() {
|
||||
error = self.close_session(error_code, &reason).await;
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Ended with error: {:?}", error);
|
||||
|
||||
match &error {
|
||||
DriverError::ApplicationClosed(_) | DriverError::LocallyClosed => {
|
||||
// Termination procedure
|
||||
// TODO Reset send sides of all streams with SessionGone error
|
||||
self.quic_connection
|
||||
.close(varint_w2q(ErrorCode::NoError.to_code()), b"");
|
||||
}
|
||||
DriverError::Proto(error_code) => {
|
||||
self.quic_connection
|
||||
.close(varint_w2q(error_code.to_code()), b"");
|
||||
}
|
||||
DriverError::NotConnected => (),
|
||||
}
|
||||
|
||||
self.driver_result.set(error);
|
||||
}
|
||||
|
||||
async fn run_impl(&mut self) -> Result<(), DriverError> {
|
||||
let mut remote_settings_watcher = self.remote_settings_stream.subscribe();
|
||||
let mut ready_uni_h3_streams = mpsc::channel(4);
|
||||
let mut ready_bi_h3_streams = mpsc::channel(1);
|
||||
|
||||
self.open_and_send_settings().await?;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = Self::accept_uni(&self.quic_connection,
|
||||
&ready_uni_h3_streams.0,
|
||||
&self.ready_uni_wt_streams) => {
|
||||
result?;
|
||||
}
|
||||
|
||||
result = Self::accept_bi(&self.quic_connection,
|
||||
&ready_bi_h3_streams.0,
|
||||
&self.ready_bi_wt_streams) => {
|
||||
result?;
|
||||
}
|
||||
|
||||
result = Self::accept_datagram(&self.quic_connection,
|
||||
&self.ready_datagrams) => {
|
||||
result?;
|
||||
}
|
||||
|
||||
uni_h3_stream = ready_uni_h3_streams.1.recv() => {
|
||||
let uni_h3_stream = uni_h3_stream.expect("Sender cannot be dropped")?;
|
||||
self.handle_uni_h3_stream(uni_h3_stream)?;
|
||||
}
|
||||
|
||||
bi_h3_stream = ready_bi_h3_streams.1.recv() => {
|
||||
let (bi_h3_stream, first_frame) = bi_h3_stream.expect("Sender cannot be dropped")?;
|
||||
self.handle_bi_h3_stream(bi_h3_stream, first_frame)?;
|
||||
}
|
||||
|
||||
|
||||
settings = remote_settings_watcher.accept_settings() => {
|
||||
let settings = settings.expect("Channel cannot be dropped");
|
||||
self.handle_remote_settings(settings)?;
|
||||
}
|
||||
|
||||
stream_session = self.ready_sessions.recv() => {
|
||||
match stream_session {
|
||||
Some(stream_session) => {
|
||||
if self.connect_stream.is_empty() {
|
||||
self.connect_stream.set_stream(stream_session);
|
||||
}
|
||||
}
|
||||
None => return Err(DriverError::NotConnected),
|
||||
};
|
||||
}
|
||||
|
||||
error = Self::run_control_streams(&mut self.local_settings_stream,
|
||||
&mut self.remote_settings_stream,
|
||||
&mut self.remote_qpack_enc_stream,
|
||||
&mut self.remote_qpack_dec_stream,
|
||||
&mut self.connect_stream) => {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
Some((error_code, reason)) = self.close_requests.recv() => {
|
||||
return Err(self.close_session(error_code, &reason).await);
|
||||
}
|
||||
|
||||
() = self.driver_result.closed() => {
|
||||
return Err(DriverError::NotConnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the session from this end (draft-ietf-webtrans-http3-16 §6): WT_CLOSE_SESSION
|
||||
/// and a FIN on the CONNECT stream, then a wait of at most [`SESSION_CLOSE_LINGER`]
|
||||
/// for the peer to close its side. The session is over from here: its result is set
|
||||
/// before the capsule is written; [`run`](Self::run) closes the connection after the
|
||||
/// wait.
|
||||
async fn close_session(&mut self, error_code: u32, reason: &str) -> DriverError {
|
||||
// `register_session` hands the CONNECT stream over before the session's
|
||||
// `Connection` exists, but this loop may not have taken it yet.
|
||||
if self.connect_stream.is_empty() {
|
||||
let registered =
|
||||
tokio::time::timeout(SESSION_CLOSE_LINGER, self.ready_sessions.recv()).await;
|
||||
if let Ok(Some(stream)) = registered {
|
||||
self.connect_stream.set_stream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
self.driver_result.set(DriverError::LocallyClosed);
|
||||
self.connect_stream
|
||||
.close(error_code, reason, SESSION_CLOSE_LINGER)
|
||||
.await;
|
||||
DriverError::LocallyClosed
|
||||
}
|
||||
|
||||
async fn open_and_send_settings(&mut self) -> Result<(), DriverError> {
|
||||
assert!(self.local_settings_stream.is_empty());
|
||||
|
||||
let stream = match Stream::open_uni(&self.quic_connection)
|
||||
.await
|
||||
.ok_or(DriverError::NotConnected)?
|
||||
.upgrade(StreamHeader::new_control())
|
||||
.await
|
||||
{
|
||||
Ok(h3_stream) => h3_stream,
|
||||
Err(ProtoWriteError::NotConnected) => return Err(DriverError::NotConnected),
|
||||
Err(ProtoWriteError::Stopped) => {
|
||||
return Err(DriverError::Proto(ErrorCode::ClosedCriticalStream));
|
||||
}
|
||||
};
|
||||
|
||||
self.local_settings_stream.set_stream(stream);
|
||||
self.local_settings_stream.send_settings().await
|
||||
}
|
||||
|
||||
async fn accept_uni(
|
||||
quic_connection: &quinn::Connection,
|
||||
ready_uni_h3_streams: &mpsc::Sender<Result<StreamUniRemoteH3, DriverError>>,
|
||||
ready_uni_wt_streams: &mpsc::Sender<StreamUniRemoteWT>,
|
||||
) -> Result<(), DriverError> {
|
||||
trace!("H3 uni queue capacity: {}", ready_uni_h3_streams.capacity());
|
||||
let h3_slot = ready_uni_h3_streams
|
||||
.clone()
|
||||
.reserve_owned()
|
||||
.await
|
||||
.expect("Receiver cannot be dropped");
|
||||
|
||||
let wt_slot = match ready_uni_wt_streams.clone().reserve_owned().await {
|
||||
Ok(wt_slot) => wt_slot,
|
||||
Err(mpsc::error::SendError(_)) => return Err(DriverError::NotConnected),
|
||||
};
|
||||
|
||||
let stream_quic = Stream::accept_uni(quic_connection)
|
||||
.await
|
||||
.ok_or(DriverError::NotConnected)?;
|
||||
|
||||
let stream_id = stream_quic.id();
|
||||
debug!("New incoming uni stream ({})", stream_id);
|
||||
|
||||
tokio::spawn(
|
||||
async move {
|
||||
let stream_h3 = match stream_quic.upgrade().await {
|
||||
Ok(stream_h3) => stream_h3,
|
||||
Err(ProtoReadError::H3(error_code)) => {
|
||||
h3_slot.send(Err(DriverError::Proto(error_code)));
|
||||
return;
|
||||
}
|
||||
Err(ProtoReadError::IO(_)) => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let stream_kind = stream_h3.kind();
|
||||
debug!("Type: {:?}", stream_kind);
|
||||
|
||||
if matches!(stream_kind, StreamKind::WebTransport) {
|
||||
let stream_wt = stream_h3.upgrade();
|
||||
wt_slot.send(stream_wt);
|
||||
} else {
|
||||
h3_slot.send(Ok(stream_h3));
|
||||
}
|
||||
}
|
||||
.instrument(debug_span!("Stream", "id={}", stream_id)),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn accept_bi(
|
||||
quic_connection: &quinn::Connection,
|
||||
ready_bi_h3_streams: &mpsc::Sender<
|
||||
Result<(StreamBiRemoteH3, Frame<'static>), DriverError>,
|
||||
>,
|
||||
ready_bi_wt_streams: &mpsc::Sender<StreamBiRemoteWT>,
|
||||
) -> Result<(), DriverError> {
|
||||
trace!("H3 bi queue capacity: {}", ready_bi_h3_streams.capacity());
|
||||
let h3_slot = ready_bi_h3_streams
|
||||
.clone()
|
||||
.reserve_owned()
|
||||
.await
|
||||
.expect("Receiver cannot be dropped");
|
||||
|
||||
let wt_slot = match ready_bi_wt_streams.clone().reserve_owned().await {
|
||||
Ok(wt_slot) => wt_slot,
|
||||
Err(mpsc::error::SendError(_)) => return Err(DriverError::NotConnected),
|
||||
};
|
||||
|
||||
let stream_quic = Stream::accept_bi(quic_connection)
|
||||
.await
|
||||
.ok_or(DriverError::NotConnected)?;
|
||||
|
||||
let stream_id = stream_quic.id();
|
||||
debug!("New incoming bi stream ({})", stream_id);
|
||||
|
||||
tokio::spawn(
|
||||
async move {
|
||||
let mut stream_h3 = stream_quic.upgrade();
|
||||
|
||||
let frame = loop {
|
||||
match stream_h3.read_frame().await {
|
||||
Ok(frame) => {
|
||||
debug!("Frame kind: {:?}", frame.kind());
|
||||
if !matches!(frame.kind(), FrameKind::Exercise(_)) {
|
||||
break frame;
|
||||
}
|
||||
}
|
||||
Err(ProtoReadError::H3(error_code)) => {
|
||||
h3_slot.send(Err(DriverError::Proto(error_code)));
|
||||
return;
|
||||
}
|
||||
Err(ProtoReadError::IO(_)) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
debug!("First frame: {:?}", frame);
|
||||
|
||||
match frame.session_id() {
|
||||
Some(session_id) => {
|
||||
let stream_wt = stream_h3.upgrade(session_id);
|
||||
wt_slot.send(stream_wt);
|
||||
}
|
||||
None => {
|
||||
h3_slot.send(Ok((stream_h3, frame)));
|
||||
}
|
||||
}
|
||||
}
|
||||
.instrument(debug_span!("Stream", "id={}", stream_id)),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn accept_datagram(
|
||||
quic_connection: &quinn::Connection,
|
||||
ready_datagrams: &mpsc::Sender<Datagram>,
|
||||
) -> Result<(), DriverError> {
|
||||
let slot = match ready_datagrams.reserve().await {
|
||||
Ok(slot) => slot,
|
||||
Err(mpsc::error::SendError(_)) => return Err(DriverError::NotConnected),
|
||||
};
|
||||
|
||||
let quic_dgram = quic_connection
|
||||
.read_datagram()
|
||||
.await
|
||||
.map_err(|_| DriverError::NotConnected)?;
|
||||
|
||||
let datagram = match Datagram::read(quic_dgram) {
|
||||
Ok(datagram) => datagram,
|
||||
Err(error_code) => return Err(DriverError::Proto(error_code)),
|
||||
};
|
||||
|
||||
trace!(
|
||||
"New incoming datagram (session_id: {})",
|
||||
datagram.session_id()
|
||||
);
|
||||
|
||||
slot.send(datagram);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_uni_h3_stream(&mut self, stream: StreamUniRemoteH3) -> Result<(), DriverError> {
|
||||
match stream.kind() {
|
||||
StreamKind::Control => {
|
||||
if !self.remote_settings_stream.is_empty() {
|
||||
return Err(DriverError::Proto(ErrorCode::StreamCreation));
|
||||
}
|
||||
|
||||
self.remote_settings_stream.set_stream(stream);
|
||||
}
|
||||
StreamKind::QPackEncoder => {
|
||||
if !self.remote_qpack_enc_stream.is_empty() {
|
||||
return Err(DriverError::Proto(ErrorCode::StreamCreation));
|
||||
}
|
||||
|
||||
self.remote_qpack_enc_stream.set_stream(stream);
|
||||
}
|
||||
StreamKind::QPackDecoder => {
|
||||
if !self.remote_qpack_dec_stream.is_empty() {
|
||||
return Err(DriverError::Proto(ErrorCode::StreamCreation));
|
||||
}
|
||||
|
||||
self.remote_qpack_dec_stream.set_stream(stream);
|
||||
}
|
||||
StreamKind::WebTransport => unreachable!(),
|
||||
StreamKind::Exercise(_) => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip_all, name = "Stream", fields(id = %stream.id()))]
|
||||
fn handle_bi_h3_stream(
|
||||
&mut self,
|
||||
mut stream: StreamBiRemoteH3,
|
||||
first_frame: Frame<'static>,
|
||||
) -> Result<(), DriverError> {
|
||||
match first_frame.kind() {
|
||||
FrameKind::Data => {
|
||||
return Err(DriverError::Proto(ErrorCode::FrameUnexpected));
|
||||
}
|
||||
FrameKind::Headers => {
|
||||
let headers = match Headers::with_frame(&first_frame) {
|
||||
Ok(headers) => headers,
|
||||
Err(error_code) => return Err(DriverError::Proto(error_code)),
|
||||
};
|
||||
|
||||
debug!("Headers: {:?}", headers);
|
||||
|
||||
let stream_session = match SessionRequest::try_from(headers) {
|
||||
Ok(session_request) => stream.into_session(session_request),
|
||||
Err(HeadersParseError::MethodNotConnect) => {
|
||||
stream
|
||||
.stop(ErrorCode::RequestRejected.to_code())
|
||||
.expect("Stream not already stopped");
|
||||
return Ok(());
|
||||
}
|
||||
// TODO(biagio): we might have more granularity with errors
|
||||
Err(_) => {
|
||||
stream
|
||||
.stop(ErrorCode::Message.to_code())
|
||||
.expect("Stream not already stopped");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
match self.ready_sessions.try_send(stream_session) {
|
||||
Ok(()) => {}
|
||||
Err(TrySendError::Full(mut stream)) => {
|
||||
debug!("Discarding session request: sessions queue is full");
|
||||
stream
|
||||
.stop(ErrorCode::RequestRejected.to_code())
|
||||
.expect("Stream not already stopped");
|
||||
}
|
||||
Err(TrySendError::Closed(_)) => return Err(DriverError::NotConnected),
|
||||
}
|
||||
}
|
||||
FrameKind::Settings => {
|
||||
return Err(DriverError::Proto(ErrorCode::FrameUnexpected));
|
||||
}
|
||||
FrameKind::WebTransport => unreachable!(),
|
||||
FrameKind::Exercise(_) => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_control_streams(
|
||||
local_settings: &mut LocalSettingsStream,
|
||||
remote_settings: &mut RemoteSettingsStream,
|
||||
remote_qpack_enc: &mut RemoteQPackEncStream,
|
||||
remote_qpack_dec: &mut RemoteQPackDecStream,
|
||||
connect_stream: &mut ConnectStream,
|
||||
) -> DriverError {
|
||||
tokio::select! {
|
||||
error = connect_stream.run() => error,
|
||||
error = local_settings.run() => error,
|
||||
error = remote_settings.run() => error,
|
||||
error = remote_qpack_enc.run() => error,
|
||||
error = remote_qpack_dec.run() => error,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_remote_settings(&mut self, settings: Settings) -> Result<(), DriverError> {
|
||||
debug!("Received: {:?}", settings);
|
||||
|
||||
match self.ready_settings.try_send(settings) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(mpsc::error::TrySendError::Closed(_)) => Err(DriverError::NotConnected),
|
||||
Err(mpsc::error::TrySendError::Full(_)) => {
|
||||
unreachable!("No more than 1 setting frame can be processed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod streams;
|
||||
pub(crate) mod utils;
|
||||
230
joule-code/joule-code-rs/vendor/wtransport/src/driver/streams/connect.rs
vendored
Normal file
230
joule-code/joule-code-rs/vendor/wtransport/src/driver/streams/connect.rs
vendored
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
use std::time::Duration;
|
||||
use tracing::debug;
|
||||
use wtransport_proto::bytes::BytesWriter;
|
||||
use wtransport_proto::bytes::IoReadError;
|
||||
use wtransport_proto::capsule::capsules;
|
||||
use wtransport_proto::capsule::Capsule;
|
||||
use wtransport_proto::capsule::{self};
|
||||
use wtransport_proto::error::ErrorCode;
|
||||
use wtransport_proto::frame::Frame;
|
||||
use wtransport_proto::frame::FrameKind;
|
||||
use wtransport_proto::varint::VarInt;
|
||||
|
||||
use super::session::StreamSession;
|
||||
use super::ProtoReadError;
|
||||
use crate::driver::DriverError;
|
||||
use crate::error::ApplicationClose;
|
||||
use std::future::pending;
|
||||
|
||||
pub struct ConnectStream {
|
||||
stream: Option<StreamSession>,
|
||||
}
|
||||
|
||||
impl ConnectStream {
|
||||
pub fn empty() -> Self {
|
||||
Self { stream: None }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.stream.is_none()
|
||||
}
|
||||
|
||||
pub fn set_stream(&mut self, stream: StreamSession) {
|
||||
self.stream = Some(stream);
|
||||
}
|
||||
|
||||
/// Closes the session from this end (draft-ietf-webtrans-http3-16 §6): one DATA frame
|
||||
/// carrying a WT_CLOSE_SESSION capsule, then a FIN, on the CONNECT stream, ahead of
|
||||
/// anything else queued on the connection. Then it waits, at most `linger`, for the
|
||||
/// peer to close its side of the CONNECT stream, as it must on receiving the capsule:
|
||||
/// the connection's close, which comes after, cannot then overtake the capsule.
|
||||
pub async fn close(&mut self, error_code: u32, reason: &str, linger: Duration) {
|
||||
let Some(mut stream) = self.stream.take() else {
|
||||
return;
|
||||
};
|
||||
let frame = Frame::new_data(close_session_capsule(error_code, reason).into());
|
||||
|
||||
let _ = tokio::time::timeout(linger, async move {
|
||||
stream.set_priority(i32::MAX);
|
||||
if stream.write_frame(frame).await.is_err() {
|
||||
return;
|
||||
}
|
||||
stream.finish().await;
|
||||
// Until the peer's FIN or reset, or the connection's end.
|
||||
while stream.read_frame().await.is_ok() {}
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> DriverError {
|
||||
let stream = match self.stream.as_mut() {
|
||||
Some(stream) => stream,
|
||||
None => pending().await,
|
||||
};
|
||||
|
||||
loop {
|
||||
return match stream.read_frame().await {
|
||||
Ok(frame) => {
|
||||
if !matches!(frame.kind(), FrameKind::Data) {
|
||||
debug!("Skipping non-data frame of kind {:?}", frame.kind());
|
||||
continue;
|
||||
}
|
||||
|
||||
let capsule = match Capsule::with_frame(&frame) {
|
||||
Some(capsule)
|
||||
if matches!(
|
||||
capsule.kind(),
|
||||
capsule::CapsuleKind::CloseWebTransportSession
|
||||
) =>
|
||||
{
|
||||
capsule
|
||||
}
|
||||
// Unknown capsule, skip it
|
||||
_ => {
|
||||
debug!(
|
||||
"Unknown capsule of kind {:?} of {}B",
|
||||
frame.kind(),
|
||||
frame.payload().len()
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let close_session =
|
||||
match capsules::CloseWebTransportSession::with_capsule(&capsule) {
|
||||
Ok(close_session) => close_session,
|
||||
Err(error_code) => return DriverError::Proto(error_code),
|
||||
};
|
||||
|
||||
// reset right away to avoid receiving additional data which requires resetting with ErrorCode::Message.
|
||||
self.stream
|
||||
.take()
|
||||
.unwrap()
|
||||
.reset(ErrorCode::NoError.to_code());
|
||||
|
||||
DriverError::ApplicationClosed(ApplicationClose::new(
|
||||
close_session.error_code(),
|
||||
close_session
|
||||
.reason()
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
.into_boxed_slice(),
|
||||
))
|
||||
}
|
||||
Err(ProtoReadError::H3(error_code)) => DriverError::Proto(error_code),
|
||||
Err(ProtoReadError::IO(io_error)) => match io_error {
|
||||
// Cleanly terminating a CONNECT stream without a CLOSE_WEBTRANSPORT_SESSION
|
||||
// capsule SHALL be semantically equivalent to terminating it with a
|
||||
// CLOSE_WEBTRANSPORT_SESSION capsule that has an error code of 0 and an empty error string.
|
||||
IoReadError::ImmediateFin => DriverError::ApplicationClosed(
|
||||
ApplicationClose::new(VarInt::from_u32(0), Box::new([])),
|
||||
),
|
||||
IoReadError::UnexpectedFin | IoReadError::Reset => {
|
||||
DriverError::Proto(ErrorCode::ClosedCriticalStream)
|
||||
}
|
||||
IoReadError::NotConnected => DriverError::NotConnected,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The longest Application Error Message a WT_CLOSE_SESSION capsule may carry, in bytes
|
||||
/// (draft-ietf-webtrans-http3-16 §6).
|
||||
pub const MAX_CLOSE_REASON: usize = 1024;
|
||||
|
||||
/// The payload of the DATA frame that carries a WT_CLOSE_SESSION capsule
|
||||
/// (draft-ietf-webtrans-http3-16 §6, in the capsule format of RFC 9297 §3.2):
|
||||
///
|
||||
/// ```text
|
||||
/// WT_CLOSE_SESSION Capsule {
|
||||
/// Type (i) = WT_CLOSE_SESSION (0x2843),
|
||||
/// Length (i),
|
||||
/// Application Error Code (32),
|
||||
/// Application Error Message (..8192),
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// A reason longer than [`MAX_CLOSE_REASON`] bytes is cut at the last UTF-8 character
|
||||
/// boundary within them, as the draft requires of a sender that truncates.
|
||||
pub fn close_session_capsule(error_code: u32, reason: &str) -> Vec<u8> {
|
||||
let mut end = reason.len().min(MAX_CLOSE_REASON);
|
||||
while !reason.is_char_boundary(end) {
|
||||
end -= 1;
|
||||
}
|
||||
let reason = &reason.as_bytes()[..end];
|
||||
|
||||
let mut capsule = Vec::with_capacity(2 + 2 + 4 + reason.len());
|
||||
let length = VarInt::from_u32(4 + reason.len() as u32);
|
||||
capsule
|
||||
.put_varint(VarInt::from_u32(0x2843))
|
||||
.and_then(|()| capsule.put_varint(length))
|
||||
.and_then(|()| capsule.put_bytes(&error_code.to_be_bytes()))
|
||||
.and_then(|()| capsule.put_bytes(reason))
|
||||
.expect("a Vec grows to fit");
|
||||
capsule
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use wtransport_proto::capsule::Capsule;
|
||||
|
||||
/// The frame's bytes as they go on the CONNECT stream.
|
||||
fn frame_bytes(error_code: u32, reason: &str) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
Frame::new_data(close_session_capsule(error_code, reason).into())
|
||||
.write(&mut out)
|
||||
.unwrap();
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_capsule_is_laid_out_as_the_draft_gives() {
|
||||
// DATA (0x00) of 18 bytes: WT_CLOSE_SESSION, 0x2843 as a two-byte varint (0x68 0x43);
|
||||
// its length, 4 + 11; the code, 32 bits big-endian; the message.
|
||||
let mut want = vec![0x00, 0x12, 0x68, 0x43, 0x0f, 0x00, 0x00, 0x00, 0x02];
|
||||
want.extend_from_slice(b"pop_missing");
|
||||
assert_eq!(frame_bytes(2, "pop_missing"), want);
|
||||
|
||||
// Code 0 and no message: the capsule is the type, a length of 4, and the code.
|
||||
assert_eq!(
|
||||
frame_bytes(0, ""),
|
||||
[0x00, 0x07, 0x68, 0x43, 0x04, 0x00, 0x00, 0x00, 0x00]
|
||||
);
|
||||
// The whole 32-bit code space.
|
||||
assert_eq!(
|
||||
&frame_bytes(u32::MAX, "")[5..],
|
||||
[0xff, 0xff, 0xff, 0xff]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_long_message_is_cut_at_a_character_boundary_within_1024_bytes() {
|
||||
// 1023 bytes of ASCII, then a two-byte character that would end at byte 1025.
|
||||
let reason = format!("{}é tail", "a".repeat(1023));
|
||||
let capsule = close_session_capsule(7, &reason);
|
||||
// The type (2 bytes), then a length of 4 + 1023 = 1027 as a two-byte varint.
|
||||
assert_eq!(&capsule[..4], [0x68, 0x43, 0x44, 0x03]);
|
||||
assert_eq!(&capsule[4..8], 7u32.to_be_bytes());
|
||||
assert_eq!(&capsule[8..], "a".repeat(1023).as_bytes());
|
||||
|
||||
let exact = "b".repeat(1024);
|
||||
assert_eq!(close_session_capsule(7, &exact).len(), 2 + 2 + 4 + 1024);
|
||||
assert_eq!(close_session_capsule(7, &"c".repeat(5000)).len(), 2 + 2 + 4 + 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_capsule_reads_back_as_the_code_and_message() {
|
||||
let frame = Frame::new_data(close_session_capsule(0x10, "GOAWAY timeout").into());
|
||||
let capsule = Capsule::with_frame(&frame).expect("a capsule");
|
||||
assert!(matches!(
|
||||
capsule.kind(),
|
||||
capsule::CapsuleKind::CloseWebTransportSession
|
||||
));
|
||||
let close = capsules::CloseWebTransportSession::with_capsule(&capsule).expect("a close");
|
||||
assert_eq!(close.error_code(), VarInt::from_u32(0x10));
|
||||
assert_eq!(close.reason(), "GOAWAY timeout");
|
||||
}
|
||||
}
|
||||
592
joule-code/joule-code-rs/vendor/wtransport/src/driver/streams/mod.rs
vendored
Normal file
592
joule-code/joule-code-rs/vendor/wtransport/src/driver/streams/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,592 @@
|
|||
use crate::driver::utils::streamid_q2w;
|
||||
use crate::driver::utils::varint_q2w;
|
||||
use crate::driver::utils::varint_w2q;
|
||||
use crate::error::ClosedStream;
|
||||
use crate::error::StreamReadError;
|
||||
use crate::error::StreamReadExactError;
|
||||
use crate::error::StreamWriteError;
|
||||
use crate::SessionId;
|
||||
use crate::StreamId;
|
||||
use crate::VarInt;
|
||||
use std::pin::Pin;
|
||||
use std::task::ready;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
use tokio::io::ReadBuf;
|
||||
use wtransport_proto::frame::Frame;
|
||||
use wtransport_proto::session::SessionRequest;
|
||||
use wtransport_proto::stream as stream_proto;
|
||||
use wtransport_proto::stream::Stream as StreamProto;
|
||||
use wtransport_proto::stream_header::StreamHeader;
|
||||
use wtransport_proto::stream_header::StreamKind;
|
||||
|
||||
pub type ProtoReadError = wtransport_proto::stream::IoReadError;
|
||||
pub type ProtoWriteError = wtransport_proto::stream::IoWriteError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AlreadyStop;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QuicSendStream(quinn::SendStream);
|
||||
|
||||
impl QuicSendStream {
|
||||
#[inline(always)]
|
||||
pub async fn write(&mut self, buf: &[u8]) -> Result<usize, StreamWriteError> {
|
||||
let written = self.0.write(buf).await?;
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub async fn write_all(&mut self, buf: &[u8]) -> Result<(), StreamWriteError> {
|
||||
self.0.write_all(buf).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub async fn finish(&mut self) -> Result<(), StreamWriteError> {
|
||||
let _ = self.0.finish();
|
||||
let result = self.stopped().await;
|
||||
if matches!(result, StreamWriteError::Closed) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_priority(&self, priority: i32) {
|
||||
let _ = self.0.set_priority(priority);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
/// # Panics
|
||||
///
|
||||
/// If `reset` was called.
|
||||
pub fn priority(&self) -> i32 {
|
||||
self.0.priority().expect("Stream has been reset")
|
||||
}
|
||||
|
||||
pub async fn stopped(&mut self) -> StreamWriteError {
|
||||
match self.0.stopped().await {
|
||||
Ok(None) => StreamWriteError::Closed,
|
||||
Ok(Some(code)) => StreamWriteError::Stopped(varint_q2w(code)),
|
||||
Err(quinn::StoppedError::ConnectionLost(_)) => StreamWriteError::NotConnected,
|
||||
Err(quinn::StoppedError::ZeroRttRejected) => StreamWriteError::QuicProto,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream> {
|
||||
self.0
|
||||
.reset(varint_w2q(error_code))
|
||||
.map_err(|_| ClosedStream)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> StreamId {
|
||||
streamid_q2w(self.0.id())
|
||||
}
|
||||
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
#[inline(always)]
|
||||
pub fn quic_stream(&self) -> &quinn::SendStream {
|
||||
&self.0
|
||||
}
|
||||
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
#[inline(always)]
|
||||
pub fn quic_stream_mut(&mut self) -> &mut quinn::SendStream {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl wtransport_proto::bytes::AsyncWrite for QuicSendStream {
|
||||
#[inline(always)]
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
tokio::io::AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio::io::AsyncWrite for QuicSendStream {
|
||||
#[inline(always)]
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
tokio::io::AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn poll_flush(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
tokio::io::AsyncWrite::poll_flush(Pin::new(&mut self.0), cx)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn poll_shutdown(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
tokio::io::AsyncWrite::poll_shutdown(Pin::new(&mut self.0), cx)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn poll_write_vectored(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
tokio::io::AsyncWrite::poll_write_vectored(Pin::new(&mut self.0), cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
tokio::io::AsyncWrite::is_write_vectored(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QuicRecvStream(quinn::RecvStream);
|
||||
|
||||
impl QuicRecvStream {
|
||||
#[inline(always)]
|
||||
pub async fn read(&mut self, buf: &mut [u8]) -> Result<Option<usize>, StreamReadError> {
|
||||
match self.0.read(buf).await? {
|
||||
Some(read) => Ok(Some(read)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub async fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), StreamReadExactError> {
|
||||
self.0
|
||||
.read_exact(buf)
|
||||
.await
|
||||
.map_err(|quic_error| match quic_error {
|
||||
quinn::ReadExactError::FinishedEarly(read) => {
|
||||
StreamReadExactError::FinishedEarly(read)
|
||||
}
|
||||
quinn::ReadExactError::ReadError(read) => StreamReadExactError::Read(read.into()),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn stop(&mut self, error_code: VarInt) -> Result<(), AlreadyStop> {
|
||||
self.0.stop(varint_w2q(error_code)).map_err(|_| AlreadyStop)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> StreamId {
|
||||
streamid_q2w(self.0.id())
|
||||
}
|
||||
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
#[inline(always)]
|
||||
pub fn quic_stream(&self) -> &quinn::RecvStream {
|
||||
&self.0
|
||||
}
|
||||
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
#[inline(always)]
|
||||
pub fn quic_stream_mut(&mut self) -> &mut quinn::RecvStream {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl wtransport_proto::bytes::AsyncRead for QuicRecvStream {
|
||||
#[inline(always)]
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
let mut buffer = ReadBuf::new(buf);
|
||||
|
||||
match ready!(tokio::io::AsyncRead::poll_read(
|
||||
Pin::new(&mut self.0),
|
||||
cx,
|
||||
&mut buffer
|
||||
)) {
|
||||
Ok(()) => Poll::Ready(Ok(buffer.filled().len())),
|
||||
Err(io_error) => Poll::Ready(Err(io_error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio::io::AsyncRead for QuicRecvStream {
|
||||
#[inline(always)]
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
tokio::io::AsyncRead::poll_read(Pin::new(&mut self.0), cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Stream<S, P> {
|
||||
stream: S,
|
||||
proto: P,
|
||||
}
|
||||
|
||||
pub mod biremote {
|
||||
use super::*;
|
||||
|
||||
pub type StreamBiRemoteQuic =
|
||||
Stream<(QuicSendStream, QuicRecvStream), stream_proto::biremote::StreamBiRemoteQuic>;
|
||||
|
||||
pub type StreamBiRemoteH3 =
|
||||
Stream<(QuicSendStream, QuicRecvStream), stream_proto::biremote::StreamBiRemoteH3>;
|
||||
|
||||
pub type StreamBiRemoteWT =
|
||||
Stream<(QuicSendStream, QuicRecvStream), stream_proto::biremote::StreamBiRemoteWT>;
|
||||
|
||||
impl StreamBiRemoteQuic {
|
||||
pub async fn accept_bi(quic_connection: &quinn::Connection) -> Option<Self> {
|
||||
let stream = quic_connection.accept_bi().await.ok()?;
|
||||
Some(Self {
|
||||
stream: (QuicSendStream(stream.0), QuicRecvStream(stream.1)),
|
||||
proto: StreamProto::accept_bi(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn upgrade(self) -> StreamBiRemoteH3 {
|
||||
StreamBiRemoteH3 {
|
||||
stream: self.stream,
|
||||
proto: self.proto.upgrade(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> StreamId {
|
||||
self.stream.0.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamBiRemoteH3 {
|
||||
pub async fn read_frame<'a>(&mut self) -> Result<Frame<'a>, ProtoReadError> {
|
||||
self.proto.read_frame_async(&mut self.stream.1).await
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, error_code: VarInt) -> Result<(), AlreadyStop> {
|
||||
self.stream.1.stop(error_code)
|
||||
}
|
||||
|
||||
pub fn upgrade(self, session_id: SessionId) -> StreamBiRemoteWT {
|
||||
StreamBiRemoteWT {
|
||||
stream: self.stream,
|
||||
proto: self.proto.upgrade(session_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> StreamId {
|
||||
self.stream.0.id()
|
||||
}
|
||||
|
||||
pub fn into_session(self, session_request: SessionRequest) -> session::StreamSession {
|
||||
session::StreamSession {
|
||||
stream: self.stream,
|
||||
proto: self.proto.into_session(session_request),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamBiRemoteWT {
|
||||
#[inline(always)]
|
||||
pub fn session_id(&self) -> SessionId {
|
||||
self.proto.session_id()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> StreamId {
|
||||
self.stream.0.id()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn into_stream(self) -> (QuicSendStream, QuicRecvStream) {
|
||||
self.stream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod bilocal {
|
||||
use super::*;
|
||||
|
||||
pub type StreamBiLocalQuic =
|
||||
Stream<(QuicSendStream, QuicRecvStream), stream_proto::bilocal::StreamBiLocalQuic>;
|
||||
|
||||
pub type StreamBiLocalH3 =
|
||||
Stream<(QuicSendStream, QuicRecvStream), stream_proto::bilocal::StreamBiLocalH3>;
|
||||
|
||||
pub type StreamBiLocalWT =
|
||||
Stream<(QuicSendStream, QuicRecvStream), stream_proto::bilocal::StreamBiLocalWT>;
|
||||
|
||||
impl StreamBiLocalQuic {
|
||||
pub async fn open_bi(quic_connection: &quinn::Connection) -> Option<Self> {
|
||||
let stream = quic_connection.open_bi().await.ok()?;
|
||||
Some(Self {
|
||||
stream: (QuicSendStream(stream.0), QuicRecvStream(stream.1)),
|
||||
proto: StreamProto::open_bi(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn upgrade(self) -> StreamBiLocalH3 {
|
||||
StreamBiLocalH3 {
|
||||
stream: self.stream,
|
||||
proto: self.proto.upgrade(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamBiLocalH3 {
|
||||
pub async fn upgrade(
|
||||
mut self,
|
||||
session_id: SessionId,
|
||||
) -> Result<StreamBiLocalWT, ProtoWriteError> {
|
||||
let proto = self
|
||||
.proto
|
||||
.upgrade_async(session_id, &mut self.stream.0)
|
||||
.await?;
|
||||
|
||||
Ok(StreamBiLocalWT {
|
||||
stream: self.stream,
|
||||
proto,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn into_session(self, session_request: SessionRequest) -> session::StreamSession {
|
||||
session::StreamSession {
|
||||
stream: self.stream,
|
||||
proto: self.proto.into_session(session_request),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamBiLocalWT {
|
||||
pub fn into_stream(self) -> (QuicSendStream, QuicRecvStream) {
|
||||
self.stream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod uniremote {
|
||||
use super::*;
|
||||
|
||||
pub type StreamUniRemoteQuic =
|
||||
Stream<QuicRecvStream, stream_proto::uniremote::StreamUniRemoteQuic>;
|
||||
|
||||
pub type StreamUniRemoteH3 = Stream<QuicRecvStream, stream_proto::uniremote::StreamUniRemoteH3>;
|
||||
|
||||
pub type StreamUniRemoteWT = Stream<QuicRecvStream, stream_proto::uniremote::StreamUniRemoteWT>;
|
||||
|
||||
impl StreamUniRemoteQuic {
|
||||
pub async fn accept_uni(quic_connection: &quinn::Connection) -> Option<Self> {
|
||||
let stream = quic_connection.accept_uni().await.ok()?;
|
||||
Some(Self {
|
||||
stream: QuicRecvStream(stream),
|
||||
proto: StreamProto::accept_uni(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn upgrade(mut self) -> Result<StreamUniRemoteH3, ProtoReadError> {
|
||||
let proto = self.proto.upgrade_async(&mut self.stream).await?;
|
||||
Ok(StreamUniRemoteH3 {
|
||||
stream: self.stream,
|
||||
proto,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> StreamId {
|
||||
self.stream.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamUniRemoteH3 {
|
||||
pub async fn read_frame<'a>(&mut self) -> Result<Frame<'a>, ProtoReadError> {
|
||||
self.proto.read_frame_async(&mut self.stream).await
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> StreamKind {
|
||||
self.proto.kind()
|
||||
}
|
||||
|
||||
pub fn upgrade(self) -> StreamUniRemoteWT {
|
||||
StreamUniRemoteWT {
|
||||
stream: self.stream,
|
||||
proto: self.proto.upgrade(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stream_mut(&mut self) -> &mut QuicRecvStream {
|
||||
&mut self.stream
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamUniRemoteWT {
|
||||
#[inline(always)]
|
||||
pub fn session_id(&self) -> SessionId {
|
||||
self.proto.session_id()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> StreamId {
|
||||
self.stream.id()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn into_stream(self) -> QuicRecvStream {
|
||||
self.stream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod unilocal {
|
||||
use super::*;
|
||||
|
||||
pub type StreamUniLocalQuic =
|
||||
Stream<QuicSendStream, stream_proto::unilocal::StreamUniLocalQuic>;
|
||||
|
||||
pub type StreamUniLocalH3 = Stream<QuicSendStream, stream_proto::unilocal::StreamUniLocalH3>;
|
||||
|
||||
pub type StreamUniLocalWT = Stream<QuicSendStream, stream_proto::unilocal::StreamUniLocalWT>;
|
||||
|
||||
impl StreamUniLocalQuic {
|
||||
pub async fn open_uni(quic_connection: &quinn::Connection) -> Option<Self> {
|
||||
let stream = quic_connection.open_uni().await.ok()?;
|
||||
Some(Self {
|
||||
stream: QuicSendStream(stream),
|
||||
proto: StreamProto::open_uni(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn upgrade(
|
||||
mut self,
|
||||
stream_header: StreamHeader,
|
||||
) -> Result<StreamUniLocalH3, ProtoWriteError> {
|
||||
let proto = self
|
||||
.proto
|
||||
.upgrade_async(stream_header, &mut self.stream)
|
||||
.await?;
|
||||
|
||||
Ok(StreamUniLocalH3 {
|
||||
stream: self.stream,
|
||||
proto,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamUniLocalH3 {
|
||||
pub async fn write_frame(&mut self, frame: Frame<'_>) -> Result<(), ProtoWriteError> {
|
||||
self.proto.write_frame_async(frame, &mut self.stream).await
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> StreamKind {
|
||||
self.proto.kind()
|
||||
}
|
||||
|
||||
pub async fn stopped(&mut self) -> StreamWriteError {
|
||||
self.stream.stopped().await
|
||||
}
|
||||
|
||||
pub fn upgrade(self) -> StreamUniLocalWT {
|
||||
StreamUniLocalWT {
|
||||
stream: self.stream,
|
||||
proto: self.proto.upgrade(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamUniLocalWT {
|
||||
pub fn into_stream(self) -> QuicSendStream {
|
||||
self.stream
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod session {
|
||||
use super::*;
|
||||
|
||||
pub type StreamSession =
|
||||
Stream<(QuicSendStream, QuicRecvStream), stream_proto::session::StreamSession>;
|
||||
|
||||
impl StreamSession {
|
||||
pub async fn read_frame<'a>(&mut self) -> Result<Frame<'a>, ProtoReadError> {
|
||||
self.proto.read_frame_async(&mut self.stream.1).await
|
||||
}
|
||||
|
||||
pub async fn write_frame(&mut self, frame: Frame<'_>) -> Result<(), ProtoWriteError> {
|
||||
self.proto
|
||||
.write_frame_async(frame, &mut self.stream.0)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, error_code: VarInt) -> Result<(), AlreadyStop> {
|
||||
self.stream.1.stop(error_code)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> StreamId {
|
||||
self.stream.0.id()
|
||||
}
|
||||
|
||||
pub fn session_id(&self) -> SessionId {
|
||||
SessionId::try_from_session_stream(self.id()).expect("Session stream must be valid")
|
||||
}
|
||||
|
||||
pub fn request(&self) -> &SessionRequest {
|
||||
self.proto.request()
|
||||
}
|
||||
|
||||
pub async fn finish(&mut self) {
|
||||
let _ = self.stream.0.finish().await;
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, error_code: VarInt) {
|
||||
let _ = self.stream.0.reset(error_code);
|
||||
}
|
||||
|
||||
pub fn set_priority(&self, priority: i32) {
|
||||
self.stream.0.set_priority(priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<quinn::WriteError> for StreamWriteError {
|
||||
fn from(error: quinn::WriteError) -> Self {
|
||||
match error {
|
||||
quinn::WriteError::Stopped(code) => StreamWriteError::Stopped(varint_q2w(code)),
|
||||
quinn::WriteError::ConnectionLost(_) | quinn::WriteError::ClosedStream => {
|
||||
StreamWriteError::NotConnected
|
||||
}
|
||||
quinn::WriteError::ZeroRttRejected => StreamWriteError::QuicProto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<quinn::ReadError> for StreamReadError {
|
||||
fn from(error: quinn::ReadError) -> Self {
|
||||
match error {
|
||||
quinn::ReadError::Reset(code) => StreamReadError::Reset(varint_q2w(code)),
|
||||
quinn::ReadError::ConnectionLost(_) | quinn::ReadError::ClosedStream => {
|
||||
StreamReadError::NotConnected
|
||||
}
|
||||
quinn::ReadError::IllegalOrderedRead => StreamReadError::QuicProto,
|
||||
quinn::ReadError::ZeroRttRejected => StreamReadError::QuicProto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod connect;
|
||||
pub mod qpack;
|
||||
pub mod settings;
|
||||
106
joule-code/joule-code-rs/vendor/wtransport/src/driver/streams/qpack.rs
vendored
Normal file
106
joule-code/joule-code-rs/vendor/wtransport/src/driver/streams/qpack.rs
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
use crate::driver::streams::uniremote::StreamUniRemoteH3;
|
||||
use crate::driver::DriverError;
|
||||
use crate::error::StreamReadError;
|
||||
use crate::error::StreamReadExactError;
|
||||
use std::future::pending;
|
||||
use wtransport_proto::error::ErrorCode;
|
||||
use wtransport_proto::stream_header::StreamKind;
|
||||
|
||||
pub struct RemoteQPackEncStream {
|
||||
stream: Option<StreamUniRemoteH3>,
|
||||
buffer: Box<[u8]>,
|
||||
}
|
||||
|
||||
impl RemoteQPackEncStream {
|
||||
pub fn empty() -> Self {
|
||||
let buffer = vec![0; 64].into_boxed_slice();
|
||||
|
||||
Self {
|
||||
stream: None,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.stream.is_none()
|
||||
}
|
||||
|
||||
pub fn set_stream(&mut self, stream: StreamUniRemoteH3) {
|
||||
assert!(matches!(stream.kind(), StreamKind::QPackEncoder));
|
||||
self.stream = Some(stream);
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> DriverError {
|
||||
let stream = match self.stream.as_mut() {
|
||||
Some(stream) => stream,
|
||||
None => pending().await,
|
||||
};
|
||||
|
||||
loop {
|
||||
match stream.stream_mut().read_exact(&mut self.buffer).await {
|
||||
Ok(()) => {}
|
||||
Err(StreamReadExactError::FinishedEarly(_)) => {
|
||||
return DriverError::Proto(ErrorCode::ClosedCriticalStream);
|
||||
}
|
||||
Err(StreamReadExactError::Read(StreamReadError::NotConnected)) => {
|
||||
return DriverError::NotConnected;
|
||||
}
|
||||
Err(StreamReadExactError::Read(StreamReadError::Reset(_))) => {
|
||||
return DriverError::Proto(ErrorCode::ClosedCriticalStream);
|
||||
}
|
||||
Err(StreamReadExactError::Read(StreamReadError::QuicProto)) => {
|
||||
return DriverError::Proto(ErrorCode::ClosedCriticalStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RemoteQPackDecStream {
|
||||
stream: Option<StreamUniRemoteH3>,
|
||||
buffer: Box<[u8]>,
|
||||
}
|
||||
|
||||
impl RemoteQPackDecStream {
|
||||
pub fn empty() -> Self {
|
||||
let buffer = vec![0; 64].into_boxed_slice();
|
||||
Self {
|
||||
stream: None,
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.stream.is_none()
|
||||
}
|
||||
|
||||
pub fn set_stream(&mut self, stream: StreamUniRemoteH3) {
|
||||
assert!(matches!(stream.kind(), StreamKind::QPackDecoder));
|
||||
self.stream = Some(stream);
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> DriverError {
|
||||
let stream = match self.stream.as_mut() {
|
||||
Some(stream) => stream,
|
||||
None => pending().await,
|
||||
};
|
||||
|
||||
loop {
|
||||
match stream.stream_mut().read_exact(&mut self.buffer).await {
|
||||
Ok(()) => {}
|
||||
Err(StreamReadExactError::FinishedEarly(_)) => {
|
||||
return DriverError::Proto(ErrorCode::ClosedCriticalStream);
|
||||
}
|
||||
Err(StreamReadExactError::Read(StreamReadError::NotConnected)) => {
|
||||
return DriverError::NotConnected;
|
||||
}
|
||||
Err(StreamReadExactError::Read(StreamReadError::Reset(_))) => {
|
||||
return DriverError::Proto(ErrorCode::ClosedCriticalStream);
|
||||
}
|
||||
Err(StreamReadExactError::Read(StreamReadError::QuicProto)) => {
|
||||
return DriverError::Proto(ErrorCode::ClosedCriticalStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
161
joule-code/joule-code-rs/vendor/wtransport/src/driver/streams/settings.rs
vendored
Normal file
161
joule-code/joule-code-rs/vendor/wtransport/src/driver/streams/settings.rs
vendored
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
use crate::driver::streams::unilocal::StreamUniLocalH3;
|
||||
use crate::driver::streams::uniremote::StreamUniRemoteH3;
|
||||
use crate::driver::streams::ProtoReadError;
|
||||
use crate::driver::streams::ProtoWriteError;
|
||||
use crate::driver::DriverError;
|
||||
use crate::error::StreamWriteError;
|
||||
use crate::VarInt;
|
||||
use std::future::pending;
|
||||
use tokio::sync::watch;
|
||||
use wtransport_proto::bytes;
|
||||
use wtransport_proto::error::ErrorCode;
|
||||
use wtransport_proto::frame::Frame;
|
||||
use wtransport_proto::frame::FrameKind;
|
||||
use wtransport_proto::settings::Settings;
|
||||
use wtransport_proto::stream_header::StreamKind;
|
||||
|
||||
pub struct LocalSettingsStream {
|
||||
stream: Option<StreamUniLocalH3>,
|
||||
settings: Settings,
|
||||
}
|
||||
|
||||
impl LocalSettingsStream {
|
||||
pub fn empty() -> Self {
|
||||
let settings = Settings::builder()
|
||||
.qpack_max_table_capacity(VarInt::from_u32(0))
|
||||
.qpack_blocked_streams(VarInt::from_u32(0))
|
||||
.enable_connect_protocol() // TODO(biagio): it would be nice to have this only for server
|
||||
.enable_webtransport()
|
||||
.enable_h3_datagrams()
|
||||
.webtransport_max_sessions(VarInt::from_u32(1))
|
||||
.build();
|
||||
|
||||
Self {
|
||||
stream: None,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.stream.is_none()
|
||||
}
|
||||
|
||||
pub fn set_stream(&mut self, stream: StreamUniLocalH3) {
|
||||
assert!(matches!(stream.kind(), StreamKind::Control));
|
||||
self.stream = Some(stream);
|
||||
}
|
||||
|
||||
pub async fn send_settings(&mut self) -> Result<(), DriverError> {
|
||||
match self
|
||||
.stream
|
||||
.as_mut()
|
||||
.expect("Cannot send settings on empty stream")
|
||||
.write_frame(self.settings.generate_frame())
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(()),
|
||||
Err(ProtoWriteError::NotConnected) => Err(DriverError::NotConnected),
|
||||
Err(ProtoWriteError::Stopped) => {
|
||||
Err(DriverError::Proto(ErrorCode::ClosedCriticalStream))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> DriverError {
|
||||
match self.stream.as_mut() {
|
||||
Some(stream) => match stream.stopped().await {
|
||||
StreamWriteError::NotConnected => DriverError::NotConnected,
|
||||
StreamWriteError::Closed | StreamWriteError::Stopped(_) => {
|
||||
DriverError::Proto(ErrorCode::ClosedCriticalStream)
|
||||
}
|
||||
StreamWriteError::QuicProto => DriverError::Proto(ErrorCode::ClosedCriticalStream),
|
||||
},
|
||||
None => pending().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RemoteSettingsStream {
|
||||
stream: Option<StreamUniRemoteH3>,
|
||||
settings: watch::Sender<Option<Settings>>,
|
||||
}
|
||||
|
||||
impl RemoteSettingsStream {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
stream: None,
|
||||
settings: watch::channel(None).0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.stream.is_none()
|
||||
}
|
||||
|
||||
pub fn set_stream(&mut self, stream: StreamUniRemoteH3) {
|
||||
assert!(matches!(stream.kind(), StreamKind::Control));
|
||||
self.stream = Some(stream);
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> RemoteSettingsWatcher {
|
||||
RemoteSettingsWatcher(self.settings.subscribe())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> DriverError {
|
||||
loop {
|
||||
let frame = match self.read_frame().await {
|
||||
Ok(frame) => frame,
|
||||
Err(driver_error) => return driver_error,
|
||||
};
|
||||
|
||||
if self.settings.borrow().is_none() {
|
||||
if !matches!(frame.kind(), FrameKind::Settings) {
|
||||
return DriverError::Proto(ErrorCode::MissingSettings);
|
||||
}
|
||||
|
||||
let settings = match Settings::with_frame(&frame) {
|
||||
Ok(settings) => settings,
|
||||
Err(error_code) => return DriverError::Proto(error_code),
|
||||
};
|
||||
|
||||
self.settings.send_replace(Some(settings));
|
||||
} else if !matches!(frame.kind(), FrameKind::Exercise(_)) {
|
||||
return DriverError::Proto(ErrorCode::FrameUnexpected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_frame<'a>(&mut self) -> Result<Frame<'a>, DriverError> {
|
||||
let Some(stream) = self.stream.as_mut() else {
|
||||
return pending().await;
|
||||
};
|
||||
|
||||
match stream.read_frame().await {
|
||||
Ok(frame) => Ok(frame),
|
||||
Err(ProtoReadError::H3(error_code)) => Err(DriverError::Proto(error_code)),
|
||||
Err(ProtoReadError::IO(io_error)) => match io_error {
|
||||
bytes::IoReadError::ImmediateFin
|
||||
| bytes::IoReadError::UnexpectedFin
|
||||
| bytes::IoReadError::Reset => {
|
||||
Err(DriverError::Proto(ErrorCode::ClosedCriticalStream))
|
||||
}
|
||||
bytes::IoReadError::NotConnected => Err(DriverError::NotConnected),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RemoteSettingsWatcher(watch::Receiver<Option<Settings>>);
|
||||
|
||||
impl RemoteSettingsWatcher {
|
||||
pub async fn accept_settings(&mut self) -> Option<Settings> {
|
||||
self.0.changed().await.ok()?;
|
||||
|
||||
Some(
|
||||
self.0
|
||||
.borrow()
|
||||
.clone()
|
||||
.expect("On change settings must be set"),
|
||||
)
|
||||
}
|
||||
}
|
||||
236
joule-code/joule-code-rs/vendor/wtransport/src/driver/utils.rs
vendored
Normal file
236
joule-code/joule-code-rs/vendor/wtransport/src/driver/utils.rs
vendored
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
use crate::StreamId;
|
||||
use crate::VarInt;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::watch;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn varint_q2w(varint: quinn::VarInt) -> VarInt {
|
||||
// SAFETY: varint conversion
|
||||
unsafe {
|
||||
debug_assert!(varint.into_inner() <= VarInt::MAX.into_inner());
|
||||
VarInt::from_u64_unchecked(varint.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn varint_w2q(varint: VarInt) -> quinn::VarInt {
|
||||
// SAFETY: varint conversion
|
||||
unsafe {
|
||||
debug_assert!(varint.into_inner() <= quinn::VarInt::MAX.into_inner());
|
||||
quinn::VarInt::from_u64_unchecked(varint.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn streamid_q2w(stream_id: quinn::StreamId) -> StreamId {
|
||||
let value = quinn::VarInt::from(stream_id).into_inner();
|
||||
|
||||
let varint = unsafe {
|
||||
debug_assert!(value <= VarInt::MAX.into_inner());
|
||||
VarInt::from_u64_unchecked(value)
|
||||
};
|
||||
|
||||
StreamId::new(varint)
|
||||
}
|
||||
|
||||
pub fn shared_result<T>() -> (SharedResultSet<T>, SharedResultGet<T>)
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
let set = SharedResultSet::new();
|
||||
let get = set.subscribe();
|
||||
(set, get)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SharedResultSet<T>(Arc<watch::Sender<Option<T>>>);
|
||||
|
||||
impl<T> SharedResultSet<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Self(Arc::new(watch::channel(None).0))
|
||||
}
|
||||
|
||||
/// Sets the shared result in thread safe manner.
|
||||
///
|
||||
/// The first call will be able to actually set the inner value,
|
||||
/// successive calls end up into being no-op.
|
||||
///
|
||||
/// Returns `true` if the inner result is actually set.
|
||||
pub fn set(&self, result: T) -> bool {
|
||||
self.0.send_if_modified(|state| {
|
||||
if state.is_none() {
|
||||
*state = Some(result);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Awaits all subscribers are dead.
|
||||
#[inline(always)]
|
||||
pub async fn closed(&self) {
|
||||
self.0.closed().await;
|
||||
}
|
||||
|
||||
/// Generates a new subscriber.
|
||||
///
|
||||
/// A subscriber is able to be notified when the shared result
|
||||
/// will be set.
|
||||
#[inline(always)]
|
||||
pub fn subscribe(&self) -> SharedResultGet<T> {
|
||||
SharedResultGet(Mutex::new(self.0.subscribe()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SharedResultGet<T>(Mutex<watch::Receiver<Option<T>>>);
|
||||
|
||||
impl<T> SharedResultGet<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
/// Awaits the shared result is set by any setter.
|
||||
///
|
||||
/// Once the shared result is set, this method will always
|
||||
/// return that value (i.e., `Some(T)`).
|
||||
///
|
||||
/// If all setters are dead before setting any result, this will
|
||||
/// return `None`. And all successive calls will return `None`.
|
||||
pub async fn result(&self) -> Option<T> {
|
||||
let mut lock = self.0.lock().await;
|
||||
|
||||
loop {
|
||||
if let Some(result) = lock.borrow().clone() {
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
if lock.changed().await.is_err() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SendError;
|
||||
|
||||
pub enum TrySendError<T> {
|
||||
Full(T),
|
||||
Closed(T),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BiChannelEndpoint<T> {
|
||||
sender: mpsc::Sender<T>,
|
||||
receiver: Mutex<mpsc::Receiver<T>>,
|
||||
}
|
||||
|
||||
impl<T> BiChannelEndpoint<T> {
|
||||
#[inline(always)]
|
||||
pub async fn send(&self, value: T) -> Result<(), SendError> {
|
||||
self.sender.send(value).await.map_err(|_| SendError)
|
||||
}
|
||||
|
||||
pub fn try_send(&self, value: T) -> Result<(), TrySendError<T>> {
|
||||
self.sender.try_send(value).map_err(|error| match error {
|
||||
mpsc::error::TrySendError::Full(value) => TrySendError::Full(value),
|
||||
mpsc::error::TrySendError::Closed(value) => TrySendError::Closed(value),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub async fn recv(&self) -> Option<T> {
|
||||
self.receiver.lock().await.recv().await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bichannel<T>(capacity: usize) -> (BiChannelEndpoint<T>, BiChannelEndpoint<T>) {
|
||||
let c1 = mpsc::channel(capacity);
|
||||
let c2 = mpsc::channel(capacity);
|
||||
|
||||
(
|
||||
BiChannelEndpoint {
|
||||
sender: c1.0,
|
||||
receiver: Mutex::new(c2.1),
|
||||
},
|
||||
BiChannelEndpoint {
|
||||
sender: c2.0,
|
||||
receiver: Mutex::new(c1.1),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use utils::poll_once;
|
||||
|
||||
#[tokio::test]
|
||||
async fn shared_result_double_set() {
|
||||
let set = SharedResultSet::new();
|
||||
assert!(set.set(1));
|
||||
assert!(!set.set(2));
|
||||
|
||||
let get = set.subscribe();
|
||||
assert!(matches!(get.result().await, Some(1)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shared_result_get_drop() {
|
||||
let set = SharedResultSet::<()>::new();
|
||||
let get = set.subscribe();
|
||||
drop(set);
|
||||
assert!(get.result().await.is_none());
|
||||
assert!(get.result().await.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shared_result_get() {
|
||||
let set = SharedResultSet::new();
|
||||
let get = set.subscribe();
|
||||
|
||||
assert!(poll_once(get.result()).await.is_none());
|
||||
|
||||
set.set(1);
|
||||
drop(set);
|
||||
|
||||
assert!(matches!(poll_once(get.result()).await.unwrap(), Some(1)));
|
||||
assert!(matches!(poll_once(get.result()).await.unwrap(), Some(1)));
|
||||
}
|
||||
|
||||
mod utils {
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
|
||||
pub async fn poll_once<F, T>(future: F) -> Option<T>
|
||||
where
|
||||
F: Future<Output = T>,
|
||||
{
|
||||
PollOnce(Box::pin(future)).await
|
||||
}
|
||||
|
||||
struct PollOnce<F>(Pin<Box<F>>);
|
||||
|
||||
impl<F, T> Future for PollOnce<F>
|
||||
where
|
||||
F: Future<Output = T>,
|
||||
{
|
||||
type Output = Option<T>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match Future::poll(self.0.as_mut(), cx) {
|
||||
Poll::Ready(result) => Poll::Ready(Some(result)),
|
||||
Poll::Pending => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
797
joule-code/joule-code-rs/vendor/wtransport/src/endpoint.rs
vendored
Normal file
797
joule-code/joule-code-rs/vendor/wtransport/src/endpoint.rs
vendored
Normal file
|
|
@ -0,0 +1,797 @@
|
|||
use crate::config::ClientConfig;
|
||||
use crate::config::DnsResolver;
|
||||
use crate::config::ServerConfig;
|
||||
use crate::connection::Connection;
|
||||
use crate::driver::streams::session::StreamSession;
|
||||
use crate::driver::streams::ProtoReadError;
|
||||
use crate::driver::streams::ProtoWriteError;
|
||||
use crate::driver::utils::varint_w2q;
|
||||
use crate::driver::Driver;
|
||||
use crate::error::ConnectingError;
|
||||
use crate::error::ConnectionError;
|
||||
use crate::VarInt;
|
||||
use quinn::TokioRuntime;
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::future::IntoFuture;
|
||||
use std::marker::PhantomData;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::SocketAddrV4;
|
||||
use std::net::SocketAddrV6;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
use tracing::debug;
|
||||
use url::Host;
|
||||
use url::Url;
|
||||
use wtransport_proto::error::ErrorCode;
|
||||
use wtransport_proto::frame::FrameKind;
|
||||
use wtransport_proto::headers::Headers;
|
||||
use wtransport_proto::session::ReservedHeader;
|
||||
use wtransport_proto::session::SessionRequest as SessionRequestProto;
|
||||
use wtransport_proto::session::SessionResponse as SessionResponseProto;
|
||||
|
||||
/// Helper structure for Endpoint types.
|
||||
pub mod endpoint_side {
|
||||
use super::*;
|
||||
|
||||
/// Type of endpoint accepting multiple WebTransport connections.
|
||||
///
|
||||
/// Use [`Endpoint::server`] to create and server-endpoint.
|
||||
pub struct Server {
|
||||
pub(super) _marker: PhantomData<()>,
|
||||
}
|
||||
|
||||
/// Type of endpoint opening a WebTransport connection.
|
||||
///
|
||||
/// Use [`Endpoint::client`] to create and client-endpoint.
|
||||
pub struct Client {
|
||||
pub(super) dns_resolver: Arc<dyn DnsResolver + Send + Sync>,
|
||||
}
|
||||
}
|
||||
|
||||
/// Entrypoint for creating client or server connections.
|
||||
///
|
||||
/// A single endpoint can be used to accept or connect multiple connections.
|
||||
/// Each endpoint internally binds an UDP socket.
|
||||
///
|
||||
/// # Server
|
||||
/// Use [`Endpoint::server`] for creating a server-side endpoint.
|
||||
/// Afterwards use the method [`Endpoint::accept`] for awaiting on incoming session request.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use anyhow::Result;
|
||||
/// # use wtransport::ServerConfig;
|
||||
/// # use wtransport::Identity;
|
||||
/// use wtransport::Endpoint;
|
||||
///
|
||||
/// # async fn run() -> Result<()> {
|
||||
/// # let config = ServerConfig::builder()
|
||||
/// # .with_bind_default(4433)
|
||||
/// # .with_identity(Identity::self_signed(["doc"]).unwrap())
|
||||
/// # .build();
|
||||
/// let server = Endpoint::server(config)?;
|
||||
/// loop {
|
||||
/// let incoming_session = server.accept().await;
|
||||
/// // Spawn task that handles client incoming session...
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Client
|
||||
/// Use [`Endpoint::client`] for creating a client-side endpoint and use [`Endpoint::connect`]
|
||||
/// to connect to a server specifying the URL.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use anyhow::Result;
|
||||
/// use wtransport::ClientConfig;
|
||||
/// use wtransport::Endpoint;
|
||||
///
|
||||
/// # async fn run() -> Result<()> {
|
||||
/// let connection = Endpoint::client(ClientConfig::default())?
|
||||
/// .connect("https://localhost:4433")
|
||||
/// .await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct Endpoint<Side> {
|
||||
endpoint: quinn::Endpoint,
|
||||
side: Side,
|
||||
}
|
||||
|
||||
impl<Side> Endpoint<Side> {
|
||||
/// Closes all of this endpoint's connections immediately and cease accepting new connections.
|
||||
pub fn close(&self, error_code: VarInt, reason: &[u8]) {
|
||||
self.endpoint.close(varint_w2q(error_code), reason);
|
||||
}
|
||||
|
||||
/// Waits for all connections on the endpoint to be cleanly shut down.
|
||||
pub async fn wait_idle(&self) {
|
||||
self.endpoint.wait_idle().await;
|
||||
}
|
||||
|
||||
/// Gets the local [`SocketAddr`] the underlying socket is bound to.
|
||||
pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
|
||||
self.endpoint.local_addr()
|
||||
}
|
||||
|
||||
/// Get the number of connections that are currently open.
|
||||
pub fn open_connections(&self) -> usize {
|
||||
self.endpoint.open_connections()
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint<endpoint_side::Server> {
|
||||
/// Constructs a *server* endpoint.
|
||||
pub fn server(server_config: ServerConfig) -> std::io::Result<Self> {
|
||||
let endpoint_config = server_config.endpoint_config;
|
||||
let quic_config = server_config.quic_config;
|
||||
let socket = server_config.bind_address_config.bind_socket()?;
|
||||
let runtime = Arc::new(TokioRuntime);
|
||||
|
||||
let endpoint = quinn::Endpoint::new(endpoint_config, Some(quic_config), socket, runtime)?;
|
||||
|
||||
Ok(Self {
|
||||
endpoint,
|
||||
side: endpoint_side::Server {
|
||||
_marker: PhantomData,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the next incoming connection attempt from a client.
|
||||
pub async fn accept(&self) -> IncomingSession {
|
||||
let quic_incoming = self
|
||||
.endpoint
|
||||
.accept()
|
||||
.await
|
||||
.expect("Endpoint cannot be closed");
|
||||
|
||||
debug!("New incoming QUIC connection");
|
||||
|
||||
IncomingSession(quic_incoming)
|
||||
}
|
||||
|
||||
/// Reloads the server configuration.
|
||||
///
|
||||
/// Useful for e.g. refreshing TLS certificates without disrupting existing connections.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `server_config` - The new configuration for the server.
|
||||
/// * `rebind` - A boolean indicating whether the server should rebind its socket.
|
||||
/// If `true`, the server will bind to a new socket with the provided configuration.
|
||||
/// If `false`, the bind address configuration will be ignored.
|
||||
pub fn reload_config(&self, server_config: ServerConfig, rebind: bool) -> std::io::Result<()> {
|
||||
if rebind {
|
||||
let socket = server_config.bind_address_config.bind_socket()?;
|
||||
self.endpoint.rebind(socket)?;
|
||||
}
|
||||
|
||||
let quic_config = server_config.quic_config;
|
||||
self.endpoint.set_server_config(Some(quic_config));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint<endpoint_side::Client> {
|
||||
/// Constructs a *client* endpoint.
|
||||
pub fn client(client_config: ClientConfig) -> std::io::Result<Self> {
|
||||
let endpoint_config = client_config.endpoint_config;
|
||||
let quic_config = client_config.quic_config;
|
||||
let socket = client_config.bind_address_config.bind_socket()?;
|
||||
let runtime = Arc::new(TokioRuntime);
|
||||
|
||||
let mut endpoint = quinn::Endpoint::new(endpoint_config, None, socket, runtime)?;
|
||||
|
||||
endpoint.set_default_client_config(quic_config);
|
||||
|
||||
Ok(Self {
|
||||
endpoint,
|
||||
side: endpoint_side::Client {
|
||||
dns_resolver: client_config.dns_resolver,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Establishes a WebTransport connection to a specified URL.
|
||||
///
|
||||
/// This method initiates a WebTransport connection to the specified URL.
|
||||
/// It validates the URL, and performs necessary steps to establish a secure connection.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `options` - Connection options specifying the URL and additional headers.
|
||||
/// It can be simply an [URL](https://en.wikipedia.org/wiki/URL) string representing
|
||||
/// the WebTransport endpoint to connect to. It must have an `https` scheme.
|
||||
/// The URL can specify either an IP address or a hostname.
|
||||
/// When specifying a hostname, the method will internally perform DNS resolution,
|
||||
/// configured with
|
||||
/// [`ClientConfigBuilder::dns_resolver`](crate::config::ClientConfigBuilder::dns_resolver).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Connect using a URL with a hostname (DNS resolution is performed):
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use anyhow::Result;
|
||||
/// # use wtransport::endpoint::endpoint_side::Client;
|
||||
/// # async fn example(endpoint: wtransport::Endpoint<Client>) -> Result<()> {
|
||||
/// let url = "https://example.com:4433/webtransport";
|
||||
/// let connection = endpoint.connect(url).await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Connect using a URL with an IP address:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use anyhow::Result;
|
||||
/// # use wtransport::endpoint::endpoint_side::Client;
|
||||
/// # async fn example(endpoint: wtransport::Endpoint<Client>) -> Result<()> {
|
||||
/// let url = "https://127.0.0.1:4343/webtransport";
|
||||
/// let connection = endpoint.connect(url).await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Connect adding an additional header:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use anyhow::Result;
|
||||
/// # use wtransport::endpoint::endpoint_side::Client;
|
||||
/// # use wtransport::endpoint::ConnectOptions;
|
||||
/// # async fn example(endpoint: wtransport::Endpoint<Client>) -> Result<()> {
|
||||
/// let options = ConnectOptions::builder("https://example.com:4433/webtransport")
|
||||
/// .add_header("Authorization", "AuthToken")
|
||||
/// .build();
|
||||
/// let connection = endpoint.connect(options).await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub async fn connect<O>(&self, options: O) -> Result<Connection, ConnectingError>
|
||||
where
|
||||
O: IntoConnectOptions,
|
||||
{
|
||||
let options = options.into_options();
|
||||
|
||||
let url = Url::parse(&options.url)
|
||||
.map_err(|parse_error| ConnectingError::InvalidUrl(parse_error.to_string()))?;
|
||||
|
||||
if url.scheme() != "https" {
|
||||
return Err(ConnectingError::InvalidUrl(
|
||||
"WebTransport URL scheme must be 'https'".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let host = url.host().expect("https scheme must have an host");
|
||||
let port = url.port().unwrap_or(443);
|
||||
|
||||
let (socket_address, server_name) = match host {
|
||||
Host::Domain(domain) => {
|
||||
let socket_address = self
|
||||
.side
|
||||
.dns_resolver
|
||||
.resolve(&format!("{domain}:{port}"))
|
||||
.await
|
||||
.map_err(ConnectingError::DnsLookup)?
|
||||
.ok_or(ConnectingError::DnsNotFound)?;
|
||||
|
||||
(socket_address, domain.to_string())
|
||||
}
|
||||
Host::Ipv4(address) => {
|
||||
let socket_address = SocketAddr::V4(SocketAddrV4::new(address, port));
|
||||
(socket_address, address.to_string())
|
||||
}
|
||||
Host::Ipv6(address) => {
|
||||
let socket_address = SocketAddr::V6(SocketAddrV6::new(address, port, 0, 0));
|
||||
(socket_address, address.to_string())
|
||||
}
|
||||
};
|
||||
|
||||
let quic_connection = self
|
||||
.endpoint
|
||||
.connect(socket_address, &server_name)
|
||||
.map_err(ConnectingError::with_connect_error)?
|
||||
.await
|
||||
.map_err(|connection_error| {
|
||||
ConnectingError::ConnectionError(connection_error.into())
|
||||
})?;
|
||||
|
||||
let driver = Driver::init(quic_connection.clone());
|
||||
|
||||
let _settings = driver.accept_settings().await.map_err(|driver_error| {
|
||||
ConnectingError::ConnectionError(ConnectionError::with_driver_error(
|
||||
driver_error,
|
||||
&quic_connection,
|
||||
))
|
||||
})?;
|
||||
|
||||
// TODO(biagio): validate settings
|
||||
|
||||
let mut session_request_proto =
|
||||
SessionRequestProto::new(url.as_ref()).expect("Url has been already validate");
|
||||
|
||||
for (k, v) in options.additional_headers {
|
||||
session_request_proto
|
||||
.insert(k.clone(), v)
|
||||
.map_err(|ReservedHeader| ConnectingError::ReservedHeader(k))?;
|
||||
}
|
||||
|
||||
let mut stream_session = match driver.open_session(session_request_proto).await {
|
||||
Ok(stream_session) => stream_session,
|
||||
Err(driver_error) => {
|
||||
return Err(ConnectingError::ConnectionError(
|
||||
ConnectionError::with_driver_error(driver_error, &quic_connection),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let session_id = stream_session.session_id();
|
||||
|
||||
match stream_session
|
||||
.write_frame(stream_session.request().headers().generate_frame())
|
||||
.await
|
||||
{
|
||||
Ok(()) => {}
|
||||
Err(ProtoWriteError::Stopped) => {
|
||||
return Err(ConnectingError::SessionRejected);
|
||||
}
|
||||
Err(ProtoWriteError::NotConnected) => {
|
||||
return Err(ConnectingError::with_no_connection(&quic_connection));
|
||||
}
|
||||
}
|
||||
|
||||
let frame = loop {
|
||||
let frame = match stream_session.read_frame().await {
|
||||
Ok(frame) => frame,
|
||||
Err(ProtoReadError::H3(error_code)) => {
|
||||
quic_connection.close(varint_w2q(error_code.to_code()), b"");
|
||||
return Err(ConnectingError::ConnectionError(
|
||||
ConnectionError::local_h3_error(error_code),
|
||||
));
|
||||
}
|
||||
Err(ProtoReadError::IO(_io_error)) => {
|
||||
return Err(ConnectingError::with_no_connection(&quic_connection));
|
||||
}
|
||||
};
|
||||
|
||||
if let FrameKind::Exercise(_) = frame.kind() {
|
||||
continue;
|
||||
}
|
||||
break frame;
|
||||
};
|
||||
|
||||
if !matches!(frame.kind(), FrameKind::Headers) {
|
||||
quic_connection.close(varint_w2q(ErrorCode::FrameUnexpected.to_code()), b"");
|
||||
return Err(ConnectingError::ConnectionError(
|
||||
ConnectionError::local_h3_error(ErrorCode::FrameUnexpected),
|
||||
));
|
||||
}
|
||||
|
||||
let headers = match Headers::with_frame(&frame) {
|
||||
Ok(headers) => headers,
|
||||
Err(error_code) => {
|
||||
quic_connection.close(varint_w2q(error_code.to_code()), b"");
|
||||
return Err(ConnectingError::ConnectionError(
|
||||
ConnectionError::local_h3_error(error_code),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(session_response) = SessionResponseProto::try_from(headers) else {
|
||||
quic_connection.close(varint_w2q(ErrorCode::Message.to_code()), b"");
|
||||
return Err(ConnectingError::ConnectionError(
|
||||
ConnectionError::local_h3_error(ErrorCode::Message),
|
||||
));
|
||||
};
|
||||
|
||||
if session_response.code().is_successful() {
|
||||
match driver.register_session(stream_session).await {
|
||||
Ok(()) => {}
|
||||
Err(driver_error) => {
|
||||
return Err(ConnectingError::ConnectionError(
|
||||
ConnectionError::with_driver_error(driver_error, &quic_connection),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ConnectingError::SessionRejected);
|
||||
}
|
||||
|
||||
Ok(Connection::new(quic_connection, driver, session_id))
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for establishing a client WebTransport connection.
|
||||
///
|
||||
/// Used in [`Endpoint::connect`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use anyhow::Result;
|
||||
/// # use wtransport::endpoint::endpoint_side::Client;
|
||||
/// # use wtransport::endpoint::ConnectOptions;
|
||||
/// # async fn example(endpoint: wtransport::Endpoint<Client>) -> Result<()> {
|
||||
/// let options = ConnectOptions::builder("https://example.com:4433/webtransport")
|
||||
/// .add_header("Authorization", "AuthToken")
|
||||
/// .build();
|
||||
/// let connection = endpoint.connect(options).await?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectOptions {
|
||||
url: String,
|
||||
additional_headers: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ConnectOptions {
|
||||
/// Creates a new `ConnectOptions` using a builder pattern.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `url` - A [URL](https://en.wikipedia.org/wiki/URL) string representing the WebTransport
|
||||
/// endpoint to connect to. It must have an `https` scheme.
|
||||
/// The URL can specify either an IP address or a hostname.
|
||||
/// When specifying a hostname, the method will internally perform DNS resolution,
|
||||
/// configured with
|
||||
/// [`ClientConfigBuilder::dns_resolver`](crate::config::ClientConfigBuilder::dns_resolver).
|
||||
pub fn builder<S>(url: S) -> ConnectRequestBuilder
|
||||
where
|
||||
S: ToString,
|
||||
{
|
||||
ConnectRequestBuilder {
|
||||
url: url.to_string(),
|
||||
additional_headers: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the URL which this will connect to.
|
||||
pub fn url(&self) -> &str {
|
||||
&self.url
|
||||
}
|
||||
|
||||
/// Gets the additional headers that will be passed when connecting.
|
||||
pub fn additional_headers(&self) -> &HashMap<String, String> {
|
||||
&self.additional_headers
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for converting types into `ConnectOptions`.
|
||||
pub trait IntoConnectOptions {
|
||||
/// Perform value-to-value conversion into [`ConnectOptions`].
|
||||
fn into_options(self) -> ConnectOptions;
|
||||
}
|
||||
|
||||
/// A builder for [`ConnectOptions`].
|
||||
///
|
||||
/// See [`ConnectOptions::builder`].
|
||||
pub struct ConnectRequestBuilder {
|
||||
url: String,
|
||||
additional_headers: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ConnectRequestBuilder {
|
||||
/// Adds a header to the connection options.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use wtransport::endpoint::ConnectOptions;
|
||||
///
|
||||
/// let options = ConnectOptions::builder("https://example.com:4433/webtransport")
|
||||
/// .add_header("Authorization", "AuthToken")
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn add_header<K, V>(mut self, key: K, value: V) -> Self
|
||||
where
|
||||
K: ToString,
|
||||
V: ToString,
|
||||
{
|
||||
self.additional_headers
|
||||
.insert(key.to_string(), value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Constructs the [`ConnectOptions`] from the builder configuration.
|
||||
pub fn build(self) -> ConnectOptions {
|
||||
ConnectOptions {
|
||||
url: self.url,
|
||||
additional_headers: self.additional_headers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoConnectOptions for ConnectRequestBuilder {
|
||||
fn into_options(self) -> ConnectOptions {
|
||||
self.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoConnectOptions for ConnectOptions {
|
||||
fn into_options(self) -> ConnectOptions {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> IntoConnectOptions for S
|
||||
where
|
||||
S: ToString,
|
||||
{
|
||||
fn into_options(self) -> ConnectOptions {
|
||||
ConnectOptions::builder(self).build()
|
||||
}
|
||||
}
|
||||
|
||||
type DynFutureIncomingSession =
|
||||
dyn Future<Output = Result<SessionRequest, ConnectionError>> + Send + Sync;
|
||||
|
||||
/// [`IntoFuture`] for an in-progress incoming connection attempt.
|
||||
///
|
||||
/// Created by [`Endpoint::accept`].
|
||||
pub struct IncomingSession(quinn::Incoming);
|
||||
|
||||
impl IncomingSession {
|
||||
/// The peer's UDP address.
|
||||
pub fn remote_address(&self) -> SocketAddr {
|
||||
self.0.remote_address()
|
||||
}
|
||||
|
||||
/// Whether the socket address that is initiating this connection has been validated.
|
||||
///
|
||||
/// This means that the sender of the initial packet has proved that they can receive traffic
|
||||
/// sent to `self.remote_address()`.
|
||||
pub fn remote_address_validated(&self) -> bool {
|
||||
self.0.remote_address_validated()
|
||||
}
|
||||
|
||||
/// Respond with a retry packet, requiring the client to retry with address validation
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `remote_address_validated()` is true.
|
||||
pub fn retry(self) {
|
||||
self.0.retry().expect("remote address already verified");
|
||||
}
|
||||
|
||||
/// Reject this incoming connection attempt.
|
||||
pub fn refuse(self) {
|
||||
self.0.refuse();
|
||||
}
|
||||
|
||||
/// Ignore this incoming connection attempt, not sending any packet in response.
|
||||
pub fn ignore(self) {
|
||||
self.0.ignore();
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for IncomingSession {
|
||||
type IntoFuture = IncomingSessionFuture;
|
||||
type Output = Result<SessionRequest, ConnectionError>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
IncomingSessionFuture::new(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Future`] for an in-progress incoming connection attempt.
|
||||
///
|
||||
/// Created by awaiting an [`IncomingSession`]
|
||||
pub struct IncomingSessionFuture(Pin<Box<DynFutureIncomingSession>>);
|
||||
|
||||
impl IncomingSessionFuture {
|
||||
/// Creates a future from [`quinn::Incoming`].
|
||||
///
|
||||
/// The returned future resolves to a [`SessionRequest`] once the QUIC connection
|
||||
/// is established and the WebTransport session has been accepted.
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
pub fn with_quic_incoming(quic_incoming: quinn::Incoming) -> Self {
|
||||
Self::new(quic_incoming)
|
||||
}
|
||||
|
||||
/// Creates a future from [`quinn::Connecting`].
|
||||
///
|
||||
/// This is useful when a [`quinn::Connecting`] is already available and needs to be
|
||||
/// driven to completion to accept an incoming WebTransport session.
|
||||
///
|
||||
/// The returned future resolves to a [`SessionRequest`] once the QUIC connection
|
||||
/// is established and the WebTransport session has been accepted.
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
pub fn with_quic_connecting(quic_connecting: quinn::Connecting) -> Self {
|
||||
Self(Box::pin(async move {
|
||||
let quic_connection = quic_connecting.await?;
|
||||
Self::accept(quic_connection).await
|
||||
}))
|
||||
}
|
||||
|
||||
fn new(quic_incoming: quinn::Incoming) -> Self {
|
||||
Self(Box::pin(async move {
|
||||
let quic_connection = quic_incoming.await?;
|
||||
Self::accept(quic_connection).await
|
||||
}))
|
||||
}
|
||||
|
||||
async fn accept(quic_connection: quinn::Connection) -> Result<SessionRequest, ConnectionError> {
|
||||
let driver = Driver::init(quic_connection.clone());
|
||||
|
||||
let _settings = driver.accept_settings().await.map_err(|driver_error| {
|
||||
ConnectionError::with_driver_error(driver_error, &quic_connection)
|
||||
})?;
|
||||
|
||||
// TODO(biagio): validate settings
|
||||
|
||||
let stream_session = driver.accept_session().await.map_err(|driver_error| {
|
||||
ConnectionError::with_driver_error(driver_error, &quic_connection)
|
||||
})?;
|
||||
|
||||
Ok(SessionRequest::new(quic_connection, driver, stream_session))
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for IncomingSessionFuture {
|
||||
type Output = Result<SessionRequest, ConnectionError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Future::poll(self.0.as_mut(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// A incoming client session request.
|
||||
///
|
||||
/// Server should use methods [`accept`](Self::accept), [`forbidden`](Self::forbidden),
|
||||
/// or [`not_found`](Self::not_found) in order to validate or reject the client request.
|
||||
pub struct SessionRequest {
|
||||
quic_connection: quinn::Connection,
|
||||
driver: Driver,
|
||||
stream_session: StreamSession,
|
||||
}
|
||||
|
||||
impl SessionRequest {
|
||||
pub(crate) fn new(
|
||||
quic_connection: quinn::Connection,
|
||||
driver: Driver,
|
||||
stream_session: StreamSession,
|
||||
) -> Self {
|
||||
Self {
|
||||
quic_connection,
|
||||
driver,
|
||||
stream_session,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the peer's UDP address.
|
||||
///
|
||||
/// **Note**: as QUIC supports migration, remote address may change
|
||||
/// during connection. Furthermore, when IPv6 support is enabled, IPv4
|
||||
/// addresses may be mapped to IPv6.
|
||||
#[inline(always)]
|
||||
pub fn remote_address(&self) -> SocketAddr {
|
||||
self.quic_connection.remote_address()
|
||||
}
|
||||
|
||||
/// Returns the `:authority` field of the request.
|
||||
pub fn authority(&self) -> &str {
|
||||
self.stream_session.request().authority()
|
||||
}
|
||||
|
||||
/// Returns the `:path` field of the request.
|
||||
pub fn path(&self) -> &str {
|
||||
self.stream_session.request().path()
|
||||
}
|
||||
|
||||
/// Returns the `origin` field of the request if present.
|
||||
pub fn origin(&self) -> Option<&str> {
|
||||
self.stream_session.request().origin()
|
||||
}
|
||||
|
||||
/// Returns the `user-agent` field of the request if present.
|
||||
pub fn user_agent(&self) -> Option<&str> {
|
||||
self.stream_session.request().user_agent()
|
||||
}
|
||||
|
||||
/// Returns all header fields associated with the request.
|
||||
pub fn headers(&self) -> &HashMap<String, String> {
|
||||
self.stream_session.request().headers().as_ref()
|
||||
}
|
||||
|
||||
/// Accepts the client request with headers.
|
||||
pub async fn accept_with_headers<I, K, V>(
|
||||
self,
|
||||
headers: I,
|
||||
) -> Result<Connection, ConnectionError>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: Into<String>,
|
||||
V: Into<String>,
|
||||
{
|
||||
self.accept_impl(
|
||||
headers
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.into(), v.into()))
|
||||
.collect(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Accepts the client request and it establishes the WebTransport session.
|
||||
pub async fn accept(self) -> Result<Connection, ConnectionError> {
|
||||
self.accept_impl(HashMap::new()).await
|
||||
}
|
||||
|
||||
/// Rejects the client request by replying with `403` status code.
|
||||
pub async fn forbidden(self) {
|
||||
self.reject(SessionResponseProto::forbidden()).await;
|
||||
}
|
||||
|
||||
/// Rejects the client request by replying with `404` status code.
|
||||
pub async fn not_found(self) {
|
||||
self.reject(SessionResponseProto::not_found()).await;
|
||||
}
|
||||
|
||||
/// Rejects the client request by replying with `429` status code.
|
||||
pub async fn too_many_requests(self) {
|
||||
self.reject(SessionResponseProto::too_many_requests()).await;
|
||||
}
|
||||
|
||||
async fn accept_impl(
|
||||
mut self,
|
||||
headers: HashMap<String, String>,
|
||||
) -> Result<Connection, ConnectionError> {
|
||||
let mut response = SessionResponseProto::ok();
|
||||
|
||||
for (key, value) in headers {
|
||||
response.add(key, value);
|
||||
}
|
||||
|
||||
self.send_response(response).await?;
|
||||
|
||||
let session_id = self.stream_session.session_id();
|
||||
|
||||
self.driver
|
||||
.register_session(self.stream_session)
|
||||
.await
|
||||
.map_err(|driver_error| {
|
||||
ConnectionError::with_driver_error(driver_error, &self.quic_connection)
|
||||
})?;
|
||||
|
||||
Ok(Connection::new(
|
||||
self.quic_connection,
|
||||
self.driver,
|
||||
session_id,
|
||||
))
|
||||
}
|
||||
|
||||
async fn reject(mut self, response: SessionResponseProto) {
|
||||
let _ = self.send_response(response).await;
|
||||
self.stream_session.finish().await;
|
||||
}
|
||||
|
||||
async fn send_response(
|
||||
&mut self,
|
||||
response: SessionResponseProto,
|
||||
) -> Result<(), ConnectionError> {
|
||||
let frame = response.headers().generate_frame();
|
||||
|
||||
match self.stream_session.write_frame(frame).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(ProtoWriteError::NotConnected) => {
|
||||
Err(ConnectionError::no_connect(&self.quic_connection))
|
||||
}
|
||||
Err(ProtoWriteError::Stopped) => {
|
||||
self.quic_connection
|
||||
.close(varint_w2q(ErrorCode::ClosedCriticalStream.to_code()), b"");
|
||||
|
||||
Err(ConnectionError::local_h3_error(
|
||||
ErrorCode::ClosedCriticalStream,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
360
joule-code/joule-code-rs/vendor/wtransport/src/error.rs
vendored
Normal file
360
joule-code/joule-code-rs/vendor/wtransport/src/error.rs
vendored
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
use crate::driver::utils::varint_q2w;
|
||||
use crate::driver::DriverError;
|
||||
use crate::VarInt;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Display;
|
||||
use std::net::SocketAddr;
|
||||
use wtransport_proto::error::ErrorCode;
|
||||
|
||||
/// An enumeration representing various errors that can occur during a WebTransport connection.
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum ConnectionError {
|
||||
/// The connection was aborted by the peer (protocol level).
|
||||
#[error("connection aborted by peer: {0}")]
|
||||
ConnectionClosed(ConnectionClose),
|
||||
|
||||
/// The peer closed the WebTransport session: with a WT_CLOSE_SESSION capsule, whose
|
||||
/// application error code and reason this carries, or with a FIN on the CONNECT stream
|
||||
/// alone, which reads as code 0 and no reason (draft-ietf-webtrans-http3-16 §6).
|
||||
#[error("session closed by peer: {0}")]
|
||||
SessionClosed(ApplicationClose),
|
||||
|
||||
/// The connection was closed by the peer (application level). Under HTTP/3 the code is
|
||||
/// an HTTP/3 error code, not the session's.
|
||||
#[error("connection closed by peer: {0}")]
|
||||
ApplicationClosed(ApplicationClose),
|
||||
|
||||
/// The connection was locally closed.
|
||||
#[error("connection locally closed")]
|
||||
LocallyClosed,
|
||||
|
||||
/// The connection was locally closed because an HTTP3 protocol violation.
|
||||
#[error("connection locally aborted: {0}")]
|
||||
LocalH3Error(H3Error),
|
||||
|
||||
/// The connection timed out.
|
||||
#[error("connection timed out")]
|
||||
TimedOut,
|
||||
|
||||
/// The connection was closed because a QUIC protocol error.
|
||||
#[error("QUIC protocol error: {0}")]
|
||||
QuicProto(QuicProtoError),
|
||||
|
||||
/// The connection could not be created because not enough of the CID space is available
|
||||
///
|
||||
/// Try using longer connection IDs.
|
||||
#[error("CIDs exhausted")]
|
||||
CidsExhausted,
|
||||
}
|
||||
|
||||
impl ConnectionError {
|
||||
pub(crate) fn with_driver_error(
|
||||
driver_error: DriverError,
|
||||
quic_connection: &quinn::Connection,
|
||||
) -> Self {
|
||||
match driver_error {
|
||||
DriverError::Proto(error_code) => Self::local_h3_error(error_code),
|
||||
DriverError::ApplicationClosed(close) => Self::SessionClosed(close),
|
||||
DriverError::LocallyClosed => Self::LocallyClosed,
|
||||
DriverError::NotConnected => Self::no_connect(quic_connection),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn no_connect(quic_connection: &quinn::Connection) -> Self {
|
||||
match quic_connection.close_reason() {
|
||||
Some(reason) => reason.into(),
|
||||
None => {
|
||||
// Connection still alive but driver reported NotConnected.
|
||||
// This race condition can occur when driver threads are processing
|
||||
// incoming streams while connection cleanup is triggered.
|
||||
ConnectionError::LocallyClosed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn local_h3_error(error_code: ErrorCode) -> Self {
|
||||
ConnectionError::LocalH3Error(H3Error { code: error_code })
|
||||
}
|
||||
}
|
||||
|
||||
/// An enumeration representing various errors that can occur during a WebTransport client connecting.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ConnectingError {
|
||||
/// URL provided for connection is not valid.
|
||||
#[error("invalid URL: {0}")]
|
||||
InvalidUrl(String),
|
||||
|
||||
/// Failure during DNS resolution.
|
||||
#[error("cannot resolve domain: {0}")]
|
||||
DnsLookup(std::io::Error),
|
||||
|
||||
/// Cannot find any DNS.
|
||||
#[error("cannot resolve domain")]
|
||||
DnsNotFound,
|
||||
|
||||
/// Connection error during handshaking.
|
||||
#[error(transparent)]
|
||||
ConnectionError(ConnectionError),
|
||||
|
||||
/// Request rejected.
|
||||
#[error("server rejected WebTransport session request")]
|
||||
SessionRejected,
|
||||
|
||||
/// Cannot use reserved key for additional headers.
|
||||
#[error("additional header '{0}' is reserved")]
|
||||
ReservedHeader(String),
|
||||
|
||||
/// The endpoint can no longer create new connections
|
||||
///
|
||||
/// Indicates that a necessary component of the endpoint has been dropped or otherwise disabled.
|
||||
#[error("endpoint stopping")]
|
||||
EndpointStopping,
|
||||
|
||||
/// The connection could not be created because not enough of the CID space is available
|
||||
///
|
||||
/// Try using longer connection IDs
|
||||
#[error("CIDs exhausted")]
|
||||
CidsExhausted,
|
||||
|
||||
/// The server name supplied was malformed
|
||||
#[error("invalid server name: {0}")]
|
||||
InvalidServerName(String),
|
||||
|
||||
/// The remote [`SocketAddr`] supplied was malformed.
|
||||
///
|
||||
/// Examples include attempting to connect to port 0, or using an inappropriate address family.
|
||||
#[error("invalid remote address: {0}")]
|
||||
InvalidRemoteAddress(SocketAddr),
|
||||
}
|
||||
|
||||
impl ConnectingError {
|
||||
pub(crate) fn with_no_connection(quic_connection: &quinn::Connection) -> Self {
|
||||
ConnectingError::ConnectionError(match quic_connection.close_reason() {
|
||||
Some(reason) => reason.into(),
|
||||
None => {
|
||||
// Connection still alive but error reported.
|
||||
// This race condition can occur when driver threads are processing
|
||||
// incoming streams while connection cleanup is triggered.
|
||||
ConnectionError::LocallyClosed
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn with_connect_error(error: quinn::ConnectError) -> Self {
|
||||
match error {
|
||||
quinn::ConnectError::EndpointStopping => ConnectingError::EndpointStopping,
|
||||
quinn::ConnectError::CidsExhausted => ConnectingError::CidsExhausted,
|
||||
quinn::ConnectError::InvalidServerName(name) => {
|
||||
ConnectingError::InvalidServerName(name)
|
||||
}
|
||||
quinn::ConnectError::InvalidRemoteAddress(socket_addr) => {
|
||||
ConnectingError::InvalidRemoteAddress(socket_addr)
|
||||
}
|
||||
quinn::ConnectError::NoDefaultClientConfig => {
|
||||
unreachable!("quic client config is internally provided")
|
||||
}
|
||||
quinn::ConnectError::UnsupportedVersion => {
|
||||
unreachable!("quic version is internally configured")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error indicating that a stream has been already finished or reset.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("closed stream")]
|
||||
pub struct ClosedStream;
|
||||
|
||||
/// An error that arise from writing to a stream.
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum StreamWriteError {
|
||||
/// Connection has been dropped.
|
||||
#[error("not connected")]
|
||||
NotConnected,
|
||||
|
||||
/// The stream was already finished or reset locally.
|
||||
#[error("stream closed")]
|
||||
Closed,
|
||||
|
||||
/// The peer is no longer accepting data on this stream.
|
||||
#[error("stream stopped (code: {0})")]
|
||||
Stopped(VarInt),
|
||||
|
||||
/// QUIC protocol error.
|
||||
#[error("QUIC protocol error")]
|
||||
QuicProto,
|
||||
}
|
||||
|
||||
/// An error that arise from reading from a stream.
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum StreamReadError {
|
||||
/// Connection has been dropped.
|
||||
#[error("not connected")]
|
||||
NotConnected,
|
||||
|
||||
/// The peer abandoned transmitting data on this stream
|
||||
#[error("stream reset (code: {0})")]
|
||||
Reset(VarInt),
|
||||
|
||||
/// QUIC protocol error.
|
||||
#[error("QUIC protocol error")]
|
||||
QuicProto,
|
||||
}
|
||||
|
||||
/// An error that arise from reading from a stream.
|
||||
#[derive(thiserror::Error, Debug, Clone)]
|
||||
pub enum StreamReadExactError {
|
||||
/// The stream finished before all bytes were read.
|
||||
#[error("stream finished too early ({0} bytes read)")]
|
||||
FinishedEarly(usize),
|
||||
|
||||
/// A read error occurred.
|
||||
#[error(transparent)]
|
||||
Read(StreamReadError),
|
||||
}
|
||||
|
||||
/// An error that arise from sending a datagram.
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum SendDatagramError {
|
||||
/// Connection has been dropped.
|
||||
#[error("not connected")]
|
||||
NotConnected,
|
||||
|
||||
/// The peer does not support receiving datagram frames.
|
||||
#[error("peer does not support datagrams")]
|
||||
UnsupportedByPeer,
|
||||
|
||||
/// The datagram is larger than the connection can currently accommodate.
|
||||
#[error("datagram payload too large")]
|
||||
TooLarge,
|
||||
}
|
||||
|
||||
/// An error that arise when opening a new stream.
|
||||
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
|
||||
pub enum StreamOpeningError {
|
||||
/// Connection has been dropped.
|
||||
#[error("not connected")]
|
||||
NotConnected,
|
||||
|
||||
/// The peer refused the stream, stopping it during initialization.
|
||||
#[error("opening stream refused")]
|
||||
Refused,
|
||||
}
|
||||
|
||||
/// Reason given by an application for closing the connection
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct ApplicationClose {
|
||||
code: VarInt,
|
||||
reason: Box<[u8]>,
|
||||
}
|
||||
|
||||
impl Display for ApplicationClose {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.reason.is_empty() {
|
||||
self.code.fmt(f)?;
|
||||
} else {
|
||||
f.write_str(&String::from_utf8_lossy(&self.reason))?;
|
||||
f.write_str(" (code ")?;
|
||||
self.code.fmt(f)?;
|
||||
f.write_str(")")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ApplicationClose {
|
||||
/// Creates a new application close reason.
|
||||
pub(crate) fn new(code: VarInt, reason: Box<[u8]>) -> Self {
|
||||
Self { code, reason }
|
||||
}
|
||||
|
||||
/// Application-specific code for close operation.
|
||||
pub fn code(&self) -> VarInt {
|
||||
self.code
|
||||
}
|
||||
|
||||
/// Data containing the reason for closing operation.
|
||||
pub fn reason(&self) -> &[u8] {
|
||||
&self.reason
|
||||
}
|
||||
}
|
||||
|
||||
/// Reason given by the transport for closing the connection.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConnectionClose(quinn::ConnectionClose);
|
||||
|
||||
impl Display for ConnectionClose {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct representing an error in the HTTP3 layer.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct H3Error {
|
||||
code: ErrorCode,
|
||||
}
|
||||
|
||||
impl Display for H3Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.code.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<quinn::ConnectionError> for ConnectionError {
|
||||
fn from(error: quinn::ConnectionError) -> Self {
|
||||
match error {
|
||||
quinn::ConnectionError::VersionMismatch => ConnectionError::QuicProto(QuicProtoError {
|
||||
code: None,
|
||||
reason: Cow::Borrowed("QUIC protocol version mismatched"),
|
||||
}),
|
||||
quinn::ConnectionError::TransportError(e) => {
|
||||
ConnectionError::QuicProto(QuicProtoError {
|
||||
code: VarInt::try_from_u64(e.code.into()).ok(),
|
||||
reason: Cow::Owned(e.reason),
|
||||
})
|
||||
}
|
||||
quinn::ConnectionError::ConnectionClosed(close) => {
|
||||
ConnectionError::ConnectionClosed(ConnectionClose(close))
|
||||
}
|
||||
quinn::ConnectionError::ApplicationClosed(close) => {
|
||||
ConnectionError::ApplicationClosed(ApplicationClose {
|
||||
code: varint_q2w(close.error_code),
|
||||
reason: close.reason.to_vec().into_boxed_slice(),
|
||||
})
|
||||
}
|
||||
quinn::ConnectionError::Reset => ConnectionError::QuicProto(QuicProtoError {
|
||||
code: None,
|
||||
reason: Cow::Borrowed("connection has been reset"),
|
||||
}),
|
||||
quinn::ConnectionError::TimedOut => ConnectionError::TimedOut,
|
||||
quinn::ConnectionError::LocallyClosed => ConnectionError::LocallyClosed,
|
||||
quinn::ConnectionError::CidsExhausted => ConnectionError::CidsExhausted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A complete specification of an error over QUIC protocol.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct QuicProtoError {
|
||||
code: Option<VarInt>,
|
||||
reason: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl Display for QuicProtoError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let code = self
|
||||
.code
|
||||
.map(|code| format!(" (code: {code})"))
|
||||
.unwrap_or_default();
|
||||
|
||||
f.write_fmt(format_args!("{}{}", self.reason, code))
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by [`Connection::export_keying_material`](crate::Connection::export_keying_material).
|
||||
///
|
||||
/// This error occurs if the requested output length is too large.
|
||||
#[derive(Debug, thiserror::Error, Clone, Eq, PartialEq)]
|
||||
#[error("cannot derive keying material as requested output length is too large")]
|
||||
pub struct ExportKeyingMaterialError;
|
||||
148
joule-code/joule-code-rs/vendor/wtransport/src/lib.rs
vendored
Normal file
148
joule-code/joule-code-rs/vendor/wtransport/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
//! WebTransport protocol implementation in *pure* Rust, *async-friendly*, and *API-simple*.
|
||||
//!
|
||||
//! For a quick start with this library, refer to [`Endpoint`].
|
||||
//!
|
||||
//! ## About WebTransport
|
||||
//! WebTransport is a modern protocol built on [QUIC](https://en.wikipedia.org/wiki/QUIC)
|
||||
//! and [HTTP/3](https://en.wikipedia.org/wiki/HTTP/3), providing an alternative to
|
||||
//! HTTP and WebSocket.
|
||||
//!
|
||||
//! It's designed for efficient client-server communication with *low latency* and
|
||||
//! bi-directional *multistream* data exchange capabilities, making it suitable for a wide range of
|
||||
//! applications.
|
||||
//!
|
||||
//! WebTransport guarantees *secure* and *reliable* communication by leveraging encryption
|
||||
//! and authentication to protect your data during transmission.
|
||||
//!
|
||||
//! WebTransport offers two key communication channels: *streams* and *datagrams*.
|
||||
//!
|
||||
//! ### Streams
|
||||
//! WebTransport streams are communication channels that provide *ordered* and
|
||||
//! *reliable* data transfer.
|
||||
//!
|
||||
//! WebTransport streams allow sending multiple sets of data at once within a single session.
|
||||
//! Each stream operates independently, ensuring that the order and reliability
|
||||
//! of one stream do not affect the others.
|
||||
//!
|
||||
//! Streams can be: *uni-directional* or *bi-directional*.
|
||||
//!
|
||||
//! *Order Preserved, Guaranteed Delivery, Flow-Controlled, Secure (All Traffic Encrypted),
|
||||
//! and Multiple Independent Streams*.
|
||||
//!
|
||||
//! ## Datagrams
|
||||
//! WebTransport datagrams are lightweight and *unordered* communication channels,
|
||||
//! prioritizing quick data exchange without guarantees of reliability or sequence.
|
||||
//!
|
||||
//! *Unordered, No Guaranteed Delivery, No Flow-Controlled, Secure (All Traffic Encrypted),
|
||||
//! Independent Messages*.
|
||||
//!
|
||||
//!
|
||||
//! # Examples
|
||||
//! Explore operational server and client examples below. The elegantly simple yet potent
|
||||
//! API empowers you to get started with minimal code.
|
||||
//!
|
||||
//! ## Server
|
||||
//! ```no_run
|
||||
//! # use anyhow::Result;
|
||||
//! use wtransport::Endpoint;
|
||||
//! use wtransport::Identity;
|
||||
//! use wtransport::ServerConfig;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> Result<()> {
|
||||
//! let config = ServerConfig::builder()
|
||||
//! .with_bind_default(4433)
|
||||
//! .with_identity(Identity::load_pemfiles("cert.pem", "key.pem").await?)
|
||||
//! .build();
|
||||
//!
|
||||
//! let server = Endpoint::server(config)?;
|
||||
//!
|
||||
//! loop {
|
||||
//! let incoming_session = server.accept().await;
|
||||
//! let incoming_request = incoming_session.await?;
|
||||
//! let connection = incoming_request.accept().await?;
|
||||
//! // ...
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//! See [repository server example](https://github.com/BiagioFesta/wtransport/blob/master/wtransport/examples/server.rs)
|
||||
//! for the complete code.
|
||||
//!
|
||||
//! ## Client
|
||||
//! ```no_run
|
||||
//! # use anyhow::Result;
|
||||
//! use wtransport::ClientConfig;
|
||||
//! use wtransport::Endpoint;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> Result<()> {
|
||||
//! let connection = Endpoint::client(ClientConfig::default())?
|
||||
//! .connect("https://localhost:4433")
|
||||
//! .await?;
|
||||
//! // ...
|
||||
//! # Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//! See [repository client example](https://github.com/BiagioFesta/wtransport/blob/master/wtransport/examples/client.rs)
|
||||
//! for the complete code.
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![warn(missing_docs, clippy::doc_markdown)]
|
||||
|
||||
/// Client and server configurations.
|
||||
pub mod config;
|
||||
|
||||
/// WebTransport connection.
|
||||
pub mod connection;
|
||||
|
||||
/// Endpoint module.
|
||||
pub mod endpoint;
|
||||
|
||||
/// Errors definitions module.
|
||||
pub mod error;
|
||||
|
||||
/// Interfaces for sending and receiving data.
|
||||
pub mod stream;
|
||||
|
||||
/// TLS specific configurations.
|
||||
pub mod tls;
|
||||
|
||||
/// Datagrams module.
|
||||
pub mod datagram;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use config::ClientConfig;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use config::ServerConfig;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use tls::Identity;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use endpoint::Endpoint;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use connection::Connection;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use stream::RecvStream;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use stream::SendStream;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use wtransport_proto::varint::VarInt;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use wtransport_proto::ids::StreamId;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use wtransport_proto::ids::SessionId;
|
||||
|
||||
pub use wtransport_proto as proto;
|
||||
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
pub use quinn;
|
||||
|
||||
mod driver;
|
||||
392
joule-code/joule-code-rs/vendor/wtransport/src/stream.rs
vendored
Normal file
392
joule-code/joule-code-rs/vendor/wtransport/src/stream.rs
vendored
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
use crate::driver::streams::bilocal::StreamBiLocalQuic;
|
||||
use crate::driver::streams::unilocal::StreamUniLocalQuic;
|
||||
use crate::driver::streams::ProtoWriteError;
|
||||
use crate::driver::streams::QuicRecvStream;
|
||||
use crate::driver::streams::QuicSendStream;
|
||||
use crate::error::ClosedStream;
|
||||
use crate::error::StreamOpeningError;
|
||||
use crate::error::StreamReadError;
|
||||
use crate::error::StreamReadExactError;
|
||||
use crate::error::StreamWriteError;
|
||||
use crate::SessionId;
|
||||
use crate::StreamId;
|
||||
use crate::VarInt;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
use tokio::io::ReadBuf;
|
||||
use wtransport_proto::stream_header::StreamHeader;
|
||||
|
||||
/// A stream that can only be used to send data.
|
||||
#[derive(Debug)]
|
||||
pub struct SendStream(QuicSendStream);
|
||||
|
||||
impl SendStream {
|
||||
#[inline(always)]
|
||||
pub(crate) fn new(stream: QuicSendStream) -> Self {
|
||||
Self(stream)
|
||||
}
|
||||
|
||||
/// Writes bytes to the stream.
|
||||
///
|
||||
/// On success, returns the number of bytes written.
|
||||
/// Congestion and flow control may cause this to be shorter than `buf.len()`,
|
||||
/// indicating that only a prefix of `buf` was written.
|
||||
#[inline(always)]
|
||||
pub async fn write(&mut self, buf: &[u8]) -> Result<usize, StreamWriteError> {
|
||||
self.0.write(buf).await
|
||||
}
|
||||
|
||||
/// Convenience method to write an entire buffer to the stream.
|
||||
#[inline(always)]
|
||||
pub async fn write_all(&mut self, buf: &[u8]) -> Result<(), StreamWriteError> {
|
||||
self.0.write_all(buf).await
|
||||
}
|
||||
|
||||
/// Shuts down the send stream gracefully.
|
||||
///
|
||||
/// No new data may be written after calling this method. Completes when the peer has
|
||||
/// acknowledged all sent data, retransmitting data as needed.
|
||||
#[inline(always)]
|
||||
pub async fn finish(&mut self) -> Result<(), StreamWriteError> {
|
||||
self.0.finish().await
|
||||
}
|
||||
|
||||
/// Returns the [`StreamId`] associated.
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> StreamId {
|
||||
self.0.id()
|
||||
}
|
||||
|
||||
/// Sets the priority of the send stream.
|
||||
///
|
||||
/// Every send stream has an initial priority of 0. Locally buffered data from streams with
|
||||
/// higher priority will be transmitted before data from streams with lower priority. Changing
|
||||
/// the priority of a stream with pending data may only take effect after that data has been
|
||||
/// transmitted. Using many different priority levels per connection may have a negative
|
||||
/// impact on performance.
|
||||
#[inline(always)]
|
||||
pub fn set_priority(&self, priority: i32) {
|
||||
self.0.set_priority(priority);
|
||||
}
|
||||
|
||||
/// Gets the priority of the send stream.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `reset` was called.
|
||||
#[inline(always)]
|
||||
pub fn priority(&self) -> i32 {
|
||||
self.0.priority()
|
||||
}
|
||||
|
||||
/// Closes the send stream immediately.
|
||||
///
|
||||
/// No new data can be written after calling this method. Locally buffered data is dropped, and
|
||||
/// previously transmitted data will no longer be retransmitted if lost. If an attempt has
|
||||
/// already been made to finish the stream, the peer may still receive all written data.
|
||||
///
|
||||
/// If called more than once, subsequent calls will result in [`ClosedStream`] error.
|
||||
#[inline(always)]
|
||||
pub fn reset(&mut self, error_code: VarInt) -> Result<(), ClosedStream> {
|
||||
self.0.reset(error_code)
|
||||
}
|
||||
|
||||
/// Passively waits for the send stream to be stopped for any reason.
|
||||
///
|
||||
/// Returns [`StreamWriteError::Closed`] if the stream was already `finish`ed or `reset`.
|
||||
///
|
||||
/// Otherwise returns [`StreamWriteError::Stopped`] with an error code from the peer.
|
||||
#[inline(always)]
|
||||
pub async fn stopped(&mut self) -> StreamWriteError {
|
||||
self.0.stopped().await
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying QUIC stream.
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
#[inline(always)]
|
||||
pub fn quic_stream(&self) -> &quinn::SendStream {
|
||||
self.0.quic_stream()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying QUIC stream.
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
#[inline(always)]
|
||||
pub fn quic_stream_mut(&mut self) -> &mut quinn::SendStream {
|
||||
self.0.quic_stream_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// A stream that can only be used to receive data.
|
||||
#[derive(Debug)]
|
||||
pub struct RecvStream(QuicRecvStream);
|
||||
|
||||
impl RecvStream {
|
||||
#[inline(always)]
|
||||
pub(crate) fn new(stream: QuicRecvStream) -> Self {
|
||||
Self(stream)
|
||||
}
|
||||
|
||||
/// Read data contiguously from the stream.
|
||||
///
|
||||
/// On success, returns the number of bytes read into `buf`.
|
||||
#[inline(always)]
|
||||
pub async fn read(&mut self, buf: &mut [u8]) -> Result<Option<usize>, StreamReadError> {
|
||||
self.0.read(buf).await
|
||||
}
|
||||
|
||||
/// Reads an exact number of bytes contiguously from the stream.
|
||||
///
|
||||
/// If the stream terminates before the entire length has been read, it
|
||||
/// returns [`StreamReadExactError::FinishedEarly`].
|
||||
pub async fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), StreamReadExactError> {
|
||||
self.0.read_exact(buf).await
|
||||
}
|
||||
|
||||
/// Stops accepting data on the stream.
|
||||
///
|
||||
/// Discards unread data and notifies the peer to stop transmitting.
|
||||
pub fn stop(mut self, error_code: VarInt) {
|
||||
let _ = self.0.stop(error_code);
|
||||
}
|
||||
|
||||
/// Returns the [`StreamId`] associated.
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> StreamId {
|
||||
self.0.id()
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying QUIC stream.
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
#[inline(always)]
|
||||
pub fn quic_stream(&self) -> &quinn::RecvStream {
|
||||
self.0.quic_stream()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the underlying QUIC stream.
|
||||
#[cfg(feature = "quinn")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
|
||||
#[inline(always)]
|
||||
pub fn quic_stream_mut(&mut self) -> &mut quinn::RecvStream {
|
||||
self.0.quic_stream_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// A bidirectional stream composed of [`SendStream`] and [`RecvStream`].
|
||||
///
|
||||
/// `BiStream` is a utility particularly useful in situations where a generic
|
||||
/// function or method expects a single object that must implement both
|
||||
/// [`AsyncRead`](tokio::io::AsyncRead) and [`AsyncWrite`](tokio::io::AsyncWrite).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tokio::io::AsyncRead;
|
||||
/// use tokio::io::AsyncWrite;
|
||||
/// use wtransport::stream::BiStream;
|
||||
///
|
||||
/// async fn do_operation<T>(io: T)
|
||||
/// where
|
||||
/// T: AsyncRead + AsyncWrite,
|
||||
/// {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// # use wtransport::Connection;
|
||||
/// # async fn run(connection: Connection) {
|
||||
/// let bi_stream = BiStream::join(connection.accept_bi().await.unwrap());
|
||||
/// do_operation(bi_stream).await;
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct BiStream((SendStream, RecvStream));
|
||||
|
||||
impl BiStream {
|
||||
/// Joins a sending stream and a receiving stream into a single `BiStream` object.
|
||||
pub fn join(s: (SendStream, RecvStream)) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
|
||||
/// Splits the bidirectional stream into its sending and receiving stream handles.
|
||||
pub fn split(self) -> (SendStream, RecvStream) {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner [`SendStream`].
|
||||
pub fn send(&self) -> &SendStream {
|
||||
&self.0 .0
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner [`SendStream`].
|
||||
pub fn send_mut(&mut self) -> &mut SendStream {
|
||||
&mut self.0 .0
|
||||
}
|
||||
|
||||
/// Returns a reference to the inner [`RecvStream`].
|
||||
pub fn recv(&self) -> &RecvStream {
|
||||
&self.0 .1
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner [`RecvStream`].
|
||||
pub fn recv_mut(&mut self) -> &mut RecvStream {
|
||||
&mut self.0 .1
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(SendStream, RecvStream)> for BiStream {
|
||||
fn from(value: (SendStream, RecvStream)) -> Self {
|
||||
Self::join(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BiStream> for (SendStream, RecvStream) {
|
||||
fn from(value: BiStream) -> Self {
|
||||
value.split()
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio::io::AsyncWrite for SendStream {
|
||||
#[inline(always)]
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
tokio::io::AsyncWrite::poll_write(Pin::new(&mut self.0), cx, buf)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||
tokio::io::AsyncWrite::poll_flush(Pin::new(&mut self.0), cx)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||
tokio::io::AsyncWrite::poll_shutdown(Pin::new(&mut self.0), cx)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn poll_write_vectored(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
tokio::io::AsyncWrite::poll_write_vectored(Pin::new(&mut self.0), cx, bufs)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
tokio::io::AsyncWrite::is_write_vectored(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio::io::AsyncRead for RecvStream {
|
||||
#[inline(always)]
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
tokio::io::AsyncRead::poll_read(Pin::new(&mut self.0), cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio::io::AsyncWrite for BiStream {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
tokio::io::AsyncWrite::poll_write(Pin::new(&mut self.0 .0), cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
tokio::io::AsyncWrite::poll_flush(Pin::new(&mut self.0 .0), cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
tokio::io::AsyncWrite::poll_shutdown(Pin::new(&mut self.0 .0), cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl tokio::io::AsyncRead for BiStream {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
tokio::io::AsyncRead::poll_read(Pin::new(&mut self.0 .1), cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
type DynFutureUniStream = dyn Future<Output = Result<SendStream, StreamOpeningError>> + Send + Sync;
|
||||
|
||||
/// [`Future`] for an in-progress opening unidirectional stream.
|
||||
///
|
||||
/// See [`Connection::open_uni`](crate::Connection::open_uni).
|
||||
pub struct OpeningUniStream(Pin<Box<DynFutureUniStream>>);
|
||||
|
||||
impl OpeningUniStream {
|
||||
pub(crate) fn new(session_id: SessionId, quic_stream: StreamUniLocalQuic) -> Self {
|
||||
Self(Box::pin(async move {
|
||||
match quic_stream
|
||||
.upgrade(StreamHeader::new_webtransport(session_id))
|
||||
.await
|
||||
{
|
||||
Ok(stream) => Ok(SendStream(stream.upgrade().into_stream())),
|
||||
Err(ProtoWriteError::NotConnected) => Err(StreamOpeningError::NotConnected),
|
||||
Err(ProtoWriteError::Stopped) => Err(StreamOpeningError::Refused),
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for OpeningUniStream {
|
||||
type Output = Result<SendStream, StreamOpeningError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Future::poll(self.0.as_mut(), cx)
|
||||
}
|
||||
}
|
||||
|
||||
type DynFutureBiStream =
|
||||
dyn Future<Output = Result<(SendStream, RecvStream), StreamOpeningError>> + Send + Sync;
|
||||
|
||||
/// [`Future`] for an in-progress opening bidirectional stream.
|
||||
///
|
||||
/// See [`Connection::open_bi`](crate::Connection::open_bi).
|
||||
pub struct OpeningBiStream(Pin<Box<DynFutureBiStream>>);
|
||||
|
||||
impl OpeningBiStream {
|
||||
pub(crate) fn new(session_id: SessionId, quic_stream: StreamBiLocalQuic) -> Self {
|
||||
Self(Box::pin(async move {
|
||||
match quic_stream.upgrade().upgrade(session_id).await {
|
||||
Ok(stream) => {
|
||||
let stream = stream.into_stream();
|
||||
Ok((SendStream::new(stream.0), RecvStream::new(stream.1)))
|
||||
}
|
||||
Err(ProtoWriteError::NotConnected) => Err(StreamOpeningError::NotConnected),
|
||||
Err(ProtoWriteError::Stopped) => Err(StreamOpeningError::Refused),
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for OpeningBiStream {
|
||||
type Output = Result<(SendStream, RecvStream), StreamOpeningError>;
|
||||
|
||||
#[inline(always)]
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
Future::poll(self.0.as_mut(), cx)
|
||||
}
|
||||
}
|
||||
1373
joule-code/joule-code-rs/vendor/wtransport/src/tls.rs
vendored
Normal file
1373
joule-code/joule-code-rs/vendor/wtransport/src/tls.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -33,6 +33,10 @@ SKIP=(
|
|||
# that workspace, against that workspace's lock; its own lock is the
|
||||
# upstream's, kept with the vendored source.
|
||||
"jouledesk/jouledesk-rs/vendor/libwebrtc"
|
||||
# A vendored crate, patched into the joule-code workspace and into joule-moq-room.
|
||||
# Cargo resolves it as part of each, against that workspace's lock; its own lock is
|
||||
# the upstream's, kept with the vendored source.
|
||||
"joule-code/joule-code-rs/vendor/wtransport"
|
||||
)
|
||||
|
||||
skipped() { local d; for d in "${SKIP[@]}"; do [ "$d" = "$1" ] && return 0; done; return 1; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue