Repository navigation
Description
Version
h3-webtransport 0.1.2
Platform
macOS 26
Summary
Safari 26.4+ and Cocoa WebKit builds using Apple's Network.framework cannot establish a usable WebTransport session with an h3-webtransport server. In our macOS 26 CI, WebTransport.ready never resolves and the browser eventually reports:
TrevRpcError: DeadlineExceeded: initial connection deadline exceeded
The same browser client completes unary, client-streaming, server-streaming, and bidirectional RPCs against C, C++, JavaScript, and Kotlin WebTransport servers. All four modes fail before measurement against the Rust server using h3-webtransport.
This appears to be an interoperability gap with the hybrid WebTransport dialect exposed by Network.framework, rather than a TLS, QUIC, certificate, origin, or application-protocol problem.
Code Sample
-
Configure an
h3server with extended CONNECT, datagrams, and WebTransport enabled:let mut builder = h3::server::builder(); builder .enable_extended_connect(true) .enable_datagram(true) .enable_webtransport(true) .max_webtransport_sessions(1);
-
Accept the extended CONNECT request with
WebTransportSession::accept. -
From Safari 26.4+ or Playwright Cocoa WebKit on macOS 26, connect with:
const transport = new WebTransport(url, { serverCertificateHashes: certificateHashes, }); await transport.ready;
-
Observe that readiness does not complete. If readiness completes with a partially compatible SETTINGS response,
createBidirectionalStream()remains pending instead.
This reproduces with a loopback address, a valid short-lived certificate hash, and an exact allowed Origin. The same setup succeeds against servers implementing the Network.framework compatibility path.
Expected Behavior
Server-side support for Network.framework's hybrid WebTransport negotiation
Actual Behavior
WebTransport fails for Safari 26.4+ and Cocoa WebKit builds using Apple's Network.framework
Additional Context
The Cocoa client sends a hybrid SETTINGS set containing:
| Setting | Codepoint | Value |
|---|---|---|
H3_DATAGRAM |
0x33 |
1 |
| draft-07 WebTransport max sessions | 0xc671706a |
1 |
draft-14 WT_MAX_SESSIONS |
0x14e9cd29 |
1 |
WT_INITIAL_MAX_DATA |
0x2b61 |
8 * 1024 * 1024 |
WT_INITIAL_MAX_STREAMS_UNI |
0x2b64 |
100 |
WT_INITIAL_MAX_STREAMS_BIDI |
0x2b65 |
100 |
It does not advertise the draft-15 WT_ENABLED setting at 0x2c7cf000.
A server that interoperates with this client must select the compatible legacy behavior and grant initial session flow control on the CONNECT stream. The working implementation sends HTTP/3 DATA frames containing these capsules after the successful CONNECT response:
| Capsule | Codepoint |
|---|---|
WT_MAX_DATA |
0x190b4d3d |
WT_MAX_STREAMS_BIDI |
0x190b4d3f |
WT_MAX_STREAMS_UNI |
0x190b4d40 |
Without those grants, Network.framework does not make the session's bidirectional streams usable.
Activity
fix(ci): exercise Cronet over explicit HTTP/3 (#109)
dgarnier commentedon Sep 6, 2026
Independent confirmation of the negotiation half of this, with data that may narrow the diagnosis — and a minimal fix now open as #363.
We hit the same wall (Safari cancelling WebTransport CONNECTs against h3-webtransport,
H3_REQUEST_CANCELLED) and A/B-tested retail Safari 26.6.2 on macOS 26 against three servers on the same machine, same page, same keychain-trusted certificate (notserverCertificateHashes— more on that below):0x2b603742/0x2b603743onlywtransport0.70xc671706a0xc671706aTwo observations relative to this issue:
Advertising draft-07
0xc671706aalone — with no flow-control capsules at all — is sufficient for retail Safari 26.6.2: sessions establish,createBidirectionalStream()works, and data flows both ways in long-lived sessions. So the capsule-grant requirement doesn't appear to attach to the draft-07 dialect; it plausibly attaches only when the server opts into draft-14 via0x14e9cd29. Corollary: a server should not advertise0x14e9cd29without implementing the capsule grants behind it.A possible confound in the repro here: it uses
serverCertificateHashes. In our testing (Safari 26.5.2 and 26.6.2), Safari fails hash-based WebTransport handshakes against servers Chrome connects to with the byte-identical certificate — independent of the SETTINGS dialect; only a trust-store-installed certificate worked. "readynever resolves" is also what that failure looks like, so re-running this repro with a locally-trusted cert may separate the two failure modes.To be clear about scope: #363 does not resolve this issue in full. It implements only the minimal subset — send draft-07
0xc671706aalongside the legacy pair, and honor it when parsing peer SETTINGS (that half matters becauseWebTransportSession::acceptrejects sessions when the client's settings lack WebTransport support, and WebKit sends only the modern codepoints). No draft-14 codepoint, no capsule grants. In practice that subset has been enough to make Safari a fully working client for our production use (a QUIC data-streaming server: many concurrent bidi streams, long-lived live sessions). The full hybrid negotiation described here —0x14e9cd29plusWT_MAX_DATA/WT_MAX_STREAMS_*grants — would be the natural follow-on, and we'd be glad to see it land on top.Update (2026-10-24): #363 is rebased onto current master now that #356 has merged (no conflicts; the patch is unchanged) and retitled to say what it enables. Re-verified on Safari 27.0.1: sessions establish and carry several concurrent bidi streams per session. One change relative to observation 2 above: on Safari 27.0.1 a
serverCertificateHasheshandshake succeeded against a freshly generated self-signed certificate that was not in the trust store, so the hash-based failure we saw on 26.5.2 and 26.6.2 appears to be fixed in 27. Scope is unchanged: #363 is still only the draft-07 negotiation subset; the draft-14 codepoint and capsule grants remain the follow-on.docs, ci: Safari works with the Go server
fix(go): support Safari by advertising WebTransport session flow cont…
Negotiate WebTransport drafts 02 to 16
Negotiate WebTransport drafts 02 to 16
Negotiate WebTransport drafts 02 to 16