# 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. ### A data stream's header written with its first bytes (12 November 2026) The release writes a data stream's header (`0x54` and the session ID on a unidirectional stream, `0x41` and the session ID on a bidirectional one, `draft-ietf-webtrans-http3-17` §4.2, §4.3) as its stream opens, each varint a write of its own, and the stream's first bytes after it. QUIC delivers a stream as a byte stream (RFC 9000 §2.2), so where the header and what follows leave in different packets a reader must join them; two other implementations' readers do not (the MoQ interop record of 12 November 2026, D30: one stops a stream whose first read lacks the session ID, H3_STREAM_CREATION_ERROR; one misparses a header that arrives ahead of the rest). Their fault, and recorded as theirs; this stack now writes so that they read it: - A stream this end opens (`open_uni`, `open_bi`) holds its header, unwritten, until its first write; that write puts what is left of the header and the bytes into QUIC in one call (quinn's `write_chunks`), so both leave in one STREAM frame. Once the header is written whole, it alone is marked as the stream's reliable prefix (quinn's `set_reliable_size`), so a reset is still sent as RESET_STREAM_AT whose Reliable Size is the header (§4.4). - `SendStream::write_all_quic` writes so with quinn's error (joule-moq-relay's WebTransport transport writes every stream through it); `write`, `write_all` and the `AsyncWrite` impls do the same. - A stream finished, reset or dropped with nothing written writes its header first (where QUIC takes it without waiting), so the peer can still tell which session the stream was for; so does `quic_stream_mut()`, so bytes written to the QUIC stream directly follow the header. - The opening futures no longer write, so they no longer fail with `NotConnected` or `Refused`; a write or a finish after the session is gone fails as it did. - `SendStream::flush_header()` and `holds_header()`. Held end to end by `crates/joule-moq-relay/tests/webtransport_stream_header.rs` (STREAM frames counted at a client of the relay, both stream kinds, through the relay's transport and through wtransport's own `SendStream`, a stream finished or reset unwritten) and `tests/reset_stream_at.rs`. ## 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.