fix(quic-interop): explicitly advertise QPACK_MAX_TABLE_CAPACITY=0
aioquic multiplexing diagnosis: runner asked for 1999 files, aioquic processed 1371 GET requests, our endpoint wrote ONLY 1 file to /downloads. The connection stayed healthy throughout (qlog shows STREAM frames flowing both ways at t=58s). Server response volume indicates each request got a 200 response. The bug: our QpackDecoder is literal-only (no dynamic table). When aioquic primes its dynamic table after a few requests and switches to dynamic-table references in subsequent response HEADERS, our decoder silently mis-parses — status comes back 0, file not written. Empty-SETTINGS gives aioquic the spec default (also 0) but it apparently doesn't strictly enforce: it still emits dynamic-refs anyway. Fix: explicitly advertise QPACK_MAX_TABLE_CAPACITY=0 + QPACK_BLOCKED_STREAMS=0 in our SETTINGS. Forces aioquic onto the literal-only QPACK path that our decoder handles correctly. A proper fix would be to implement QPACK dynamic-table support in our decoder + read the server's encoder stream; that's its own project. For now this gets multiplexing past the QPACK barrier so we can see whether anything else is broken downstream. https://claude.ai/code/session_01HcvfQq1ttPV9PkRoJb4nyT
This commit is contained in:
+19
-3
@@ -27,6 +27,7 @@ import com.vitorpamplona.quic.http3.Http3Frame
|
||||
import com.vitorpamplona.quic.http3.Http3FrameReader
|
||||
import com.vitorpamplona.quic.http3.Http3FrameType
|
||||
import com.vitorpamplona.quic.http3.Http3Settings
|
||||
import com.vitorpamplona.quic.http3.Http3SettingsId
|
||||
import com.vitorpamplona.quic.http3.Http3StreamType
|
||||
import com.vitorpamplona.quic.qpack.QpackDecoder
|
||||
import com.vitorpamplona.quic.qpack.QpackEncoder
|
||||
@@ -62,12 +63,27 @@ class Http3GetClient(
|
||||
private val conn: QuicConnection,
|
||||
) : GetClient {
|
||||
suspend fun init(scope: CoroutineScope) {
|
||||
// Control stream: type-0x00 prefix followed by a SETTINGS frame
|
||||
// (empty body is legal — RFC 9114 §7.2.4).
|
||||
// Control stream: type-0x00 prefix followed by a SETTINGS frame.
|
||||
// Explicitly advertise QPACK_MAX_TABLE_CAPACITY=0 and
|
||||
// QPACK_BLOCKED_STREAMS=0 — our QpackDecoder is literal-only
|
||||
// (no dynamic-table support, RFC 9204 Required Insert Count = 0).
|
||||
// Empty SETTINGS would let the spec default kick in (also 0),
|
||||
// but aioquic's behavior on multiplexing showed that without
|
||||
// explicit signal it still emits dynamic-table-referenced fields
|
||||
// in response HEADERS, which our decoder silently mis-parses
|
||||
// (status=0, file not written). Sending the explicit zeros
|
||||
// forces aioquic onto the literal-only QPACK path.
|
||||
val control = conn.openUniStream()
|
||||
val w = QuicWriter()
|
||||
w.writeVarint(Http3StreamType.CONTROL)
|
||||
w.writeBytes(Http3Settings(emptyMap()).encodeFrame())
|
||||
w.writeBytes(
|
||||
Http3Settings(
|
||||
mapOf(
|
||||
Http3SettingsId.QPACK_MAX_TABLE_CAPACITY to 0L,
|
||||
Http3SettingsId.QPACK_BLOCKED_STREAMS to 0L,
|
||||
),
|
||||
).encodeFrame(),
|
||||
)
|
||||
control.send.enqueue(w.toByteArray())
|
||||
// Control stream stays open for the lifetime of the H3 connection;
|
||||
// do NOT call finish() — peers treat that as H3_CLOSED_CRITICAL_STREAM.
|
||||
|
||||
Reference in New Issue
Block a user