Connection::close_session(code, reason) has the driver's worker write one DATA frame holding a WT_CLOSE_SESSION capsule (draft-ietf-webtrans-http3-16 §6) on the CONNECT stream at the highest send priority, FIN it, and wait at most two seconds for the peer to close its side before closing the QUIC connection with H3_NO_ERROR, so the connection's close cannot overtake the capsule. The reason is cut to 1024 bytes at a character boundary. After it, this end opens no stream and sends no datagram; a close asked for just as the last handle drops still goes out. A session the peer closes is ConnectionError::SessionClosed (the release reported it as ApplicationClosed, the variant of the QUIC connection's close, whose code under HTTP/3 is an HTTP/3 error code), and Connection::closed() reports the session's end as the driver saw it. Unit tests of the capsule's bytes against the draft's layout. The upstream's examples and dev-dependencies are left out so the crate's unit tests run from the workspace (cargo test -p wtransport --lib). VENDORED.md lists every change. Co-Authored-By: Claude Opus 5.5 <[email protected]>
4.1 KiB
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, insrc/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_sessionthis end opens no stream and sends no datagram (§6):open_uni,open_biandsend_datagramfail 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::runtakes 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 asApplicationClosed, 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:SessionClosedwith the peer's code and reason, orLocallyClosedas soon as this end's capsule is queued. The release'sclosed()gave the QUIC close, so a peer's capsule was never reported there.- The tokio feature
timeis 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.