test(core): add HTTP/2 compliance suite
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# HTTP/2 compliance
|
||||
|
||||
This document records the repeatable protocol gate for Flash's HTTP/2 server. The automated
|
||||
matrix runs from Maven; external tools are selected through system properties so local builds
|
||||
without them skip only the corresponding interoperability adapter. CI installs and enables every
|
||||
command-line client listed below.
|
||||
|
||||
## h2spec
|
||||
|
||||
Validated on 2026-08-13 with h2spec 2.6.0.
|
||||
|
||||
| Listener | Cases | Failures | Skips |
|
||||
|---|---:|---:|---:|
|
||||
| TLS with ALPN `h2` | 146 | 0 | 0 |
|
||||
| Cleartext prior knowledge on the mixed HTTP/1.1 + HTTP/2 port | 145 | 0 | 0 |
|
||||
|
||||
`H2SpecComplianceTest` parses h2spec's JUnit XML and fails on a failure, error, or skipped case.
|
||||
The cleartext selection omits only `http2/3.5/2`, which sends a complete invalid HTTP/2 preface.
|
||||
That case assumes a dedicated HTTP/2 endpoint. Flash deliberately has one cleartext port that
|
||||
selects HTTP/2 only when the 24-byte prior-knowledge preface matches; any other initial bytes are
|
||||
HTTP/1.1 input. RFC 9113 section 3.3 defines the exact preface as the cleartext protocol selector,
|
||||
while section 3.4's `PROTOCOL_ERROR` applies after an endpoint is operating as HTTP/2. The HTTP/2
|
||||
state machine itself does return `GOAWAY(PROTOCOL_ERROR)` for a complete invalid preface, covered
|
||||
byte-for-byte by `invalid-preface.hex`. Excluding the mixed-port negotiation case therefore does
|
||||
not waive an HTTP/2 state-machine requirement.
|
||||
|
||||
## Interoperability
|
||||
|
||||
Automated results recorded on 2026-08-13:
|
||||
|
||||
| Client | Version | Mode and coverage | Result |
|
||||
|---|---|---|---|
|
||||
| curl | 8.5.0, libnghttp2 1.59.0 | TLS and h2c; GET, POST, 2 MiB upload/download | pass |
|
||||
| Java `HttpClient` | Temurin 21.0.11+10 | TLS; GET, POST, large bodies and multiplexing | pass |
|
||||
| nghttp | nghttp2 1.59.0 | TLS and h2c; verbose SETTINGS/HEADERS/DATA trace, POST and 2 MiB download | pass |
|
||||
| grpcurl | 1.9.3 | h2c; unary, server-streaming, client-streaming, bidi and error trailers | pass |
|
||||
|
||||
The 1,000-stream test uses one TCP connection and admits at most the advertised 64 live streams
|
||||
at once. This tests 1,000 multiplexed stream lifecycles without contradicting
|
||||
`SETTINGS_MAX_CONCURRENT_STREAMS` or weakening the production memory bound.
|
||||
|
||||
Chrome and Firefox are a release smoke test rather than a CI dependency. For each release, record
|
||||
the exact stable browser versions and date in the release evidence, then verify:
|
||||
|
||||
1. Load a TLS route and confirm `h2` in the browser network protocol column.
|
||||
2. Exercise GET, POST, a large upload and a large streamed download.
|
||||
3. Open the same registered WebSocket route over HTTP/1.1 and RFC 8441, exchange a fragmented
|
||||
message larger than one flow-control window, and close from each side once.
|
||||
4. Confirm no certificate, console, failed-request, or retry-to-HTTP/1.1 warnings.
|
||||
|
||||
This manual row is intentionally not represented as an automated pass: browser release testing
|
||||
must record the browsers actually shipped at release time rather than a stale development image.
|
||||
|
||||
## Fuzzing and regression corpus
|
||||
|
||||
All fuzz targets use deterministic xorshift or `Random` seeds, fixed maximum input lengths, an
|
||||
absolute JUnit time budget, and a post-GC retained-heap assertion. Untyped runtime failures fail
|
||||
the test immediately. The permanent targets cover:
|
||||
|
||||
| Target | Cases | Seed |
|
||||
|---|---:|---|
|
||||
| frame reader | 10,000,000 | `0x485532445f465a32` |
|
||||
| HPACK decoder | 10,000,000 | `0x75419113c0de` |
|
||||
| Huffman decoder | 1,000,000 | `0x7541485546464d4e` |
|
||||
| pseudo-header validator | 250,000 | `0x911350534555444f` |
|
||||
| HTTP/1 request parser | 25,000 | `0x911248545450314c` |
|
||||
|
||||
Exact wire inputs for implementation defects live under
|
||||
`src/test/resources/http2/regressions/`; `Http2RegressionCorpusTest` executes every file and
|
||||
asserts the terminal frame and error code. The nightly `Http2SoakTest` defaults to ten minutes of
|
||||
GET, POST, streaming DATA, reset and PING traffic, with retained-heap assertions. A short run can
|
||||
be requested with `-Dflash.http2.soak=true -Dflash.http2.soak.seconds=10`.
|
||||
|
||||
## Deliberately absent features
|
||||
|
||||
- HTTP/2 server push is not exposed. A client cannot send `PUSH_PROMISE` to a server (RFC 9113
|
||||
section 6.6); receiving one is a connection error. Flash does not originate push.
|
||||
- RFC 7540 dependency-tree priority scheduling is not implemented. RFC 9113 section 5.3.2
|
||||
deprecates the scheme; PRIORITY frames are validated and ignored as required.
|
||||
- `Upgrade: h2c` is not implemented. RFC 9113 section 3.1 removed the HTTP/1.1 upgrade mechanism;
|
||||
cleartext support uses section 3.3 prior knowledge.
|
||||
|
||||
These omissions do not create alternate request/response APIs: HTTP/1.1 and HTTP/2 remain peers
|
||||
behind the transport protocol boundary.
|
||||
@@ -1064,3 +1064,39 @@ usable by other streaming adapters.
|
||||
stream pair without losing protocol semantics.
|
||||
|
||||
---
|
||||
|
||||
## DEC-33 — Retain bounded closed-stream provenance
|
||||
|
||||
**Context.** RFC 9113 assigns different outcomes to a frame on an idle lower-numbered stream, a
|
||||
normally closed stream, and a reset stream. Removing a stream from the live table discarded the
|
||||
only information that distinguished those cases.
|
||||
|
||||
**Decision.** Keep a primitive circular tombstone table sized to twice the maximum live-stream
|
||||
count. Each entry stores only a stream id and whether it closed normally or by reset.
|
||||
|
||||
**Consequence.** The demultiplexer produces the required connection- or stream-scoped error
|
||||
without an unbounded set, boxed keys, or hot-path allocation. Very old tombstones expire, which is
|
||||
safe because a peer cannot require unbounded historical state from a bounded connection.
|
||||
|
||||
**Revisit when.** Only if a conformance case demonstrates that the bounded history is too short;
|
||||
change the fixed ratio from evidence rather than introducing an unbounded map.
|
||||
|
||||
---
|
||||
|
||||
## DEC-34 — Test cleartext conformance at the protocol-selection boundary
|
||||
|
||||
**Context.** h2spec's invalid-preface case assumes a dedicated HTTP/2 socket. Flash intentionally
|
||||
multiplexes HTTP/1.1 and HTTP/2 prior knowledge on one cleartext port, so non-matching initial
|
||||
bytes select the HTTP/1 parser before an HTTP/2 state machine exists.
|
||||
|
||||
**Decision.** Run every h2spec case applicable after prior-knowledge selection on the mixed port,
|
||||
and separately feed a complete invalid preface directly to the HTTP/2 state-machine regression
|
||||
test, where it must produce `GOAWAY(PROTOCOL_ERROR)`.
|
||||
|
||||
**Consequence.** The suite tests both layers according to their actual ownership and does not add
|
||||
a second h2-only cleartext listener solely to satisfy a tool assumption.
|
||||
|
||||
**Revisit when.** If Flash introduces a dedicated cleartext HTTP/2 listener, run the omitted case
|
||||
against that listener too.
|
||||
|
||||
---
|
||||
|
||||
@@ -77,7 +77,7 @@ Status values: `not started` / `in progress` / `blocked` / `done`.
|
||||
| 13 — Security hardening & abuse resistance | done | `feature/core/http2` | Two-bucket Rapid Reset/stream/settings/ping/aggregate counters, control/write queue bounds, optional stream/byte/lifetime budgets, absolute header and idle-stream deadlines, and hostile-peer suite complete. Security review found+fixed EX-50/51. JMH counter: 38.083 ns/op, ~10^-4 B/op, no GC. 100k-CONTINUATION attack terminates in under 2 s with bounded retained heap. 663/663 tests green from a clean `-Pjmh` build. |
|
||||
| 14 — h2c prior knowledge + proxy support | done | `feature/core/http2` | Independent TLS/h2c gates, pooled proxy-oriented h2 client with TLS ALPN and h2c, bidirectional h1/h2 trailer relay, shared four-direction hop-by-hop policy and certificate-backed 421 handling complete. Real grpcurl h2c interop passes. 670/670 tests green from a clean `-Pjmh` build. |
|
||||
| 15 — RFC 8441 extended CONNECT (WS over h2) | done | `feature/core/http2` | SETTINGS_ENABLE_CONNECT_PROTOCOL, shared WS router/session, DATA flow control, >1 MiB message, h1/h2 parity and lifecycle hardening complete. EX-52/53 fixed; DEC-32 recorded. 675/675 tests green from a clean `-Pjmh` build; real grpcurl interop remains green. |
|
||||
| 16 — Compliance test suite | not started | — | — |
|
||||
| 16 — Compliance test suite | done | `feature/core/http2` | h2spec 2.6.0: TLS 146/146 and mixed-port h2c 145/145 applicable cases, zero skips/failures; invalid-preface protocol boundary documented and regression-tested. Deterministic bounded fuzz targets, exact wire corpus, 1,000-stream single-connection test, nightly 10-minute soak, curl/nghttp/Java/grpcurl matrix and release-browser checklist complete. EX-54–56 fixed; DEC-33/34 recorded. Clean `-Pjmh` gate: 690 tests, 0 failures/errors, 1 intentional conditional soak skip. |
|
||||
| 17 — Benchmarks, allocation gates, tuning | not started | — | — |
|
||||
| 18 — Documentation | not started | — | — |
|
||||
|
||||
@@ -832,6 +832,29 @@ batch and start body reads only from the post-write resume batch. `WebSocketOver
|
||||
handshake completes before sending a message and then carries a message beyond the flow window.
|
||||
**Phase**: 15.
|
||||
|
||||
### EX-54 — HEADERS on a half-closed-remote stream were decoded as trailers before state validation
|
||||
|
||||
Found by the complete Phase 16 h2spec run. `receiveHeaders` entered trailer validation before
|
||||
checking `HALF_CLOSED_REMOTE`, producing the wrong error scope and, for some blocks, waiting for
|
||||
irrelevant trailer completion. **Fix**: reject immediately with a stream-scoped `STREAM_CLOSED`.
|
||||
The h2spec case and exact regression frame sequence cover the ordering. **Phase**: 16.
|
||||
|
||||
### EX-55 — Retiring a stream discarded the provenance needed for lower stream-id errors
|
||||
|
||||
Found by h2spec closed-stream cases. Once a stream left the live table, the connection could not
|
||||
distinguish a never-opened lower id, a normally closed stream, and a reset stream, although RFC
|
||||
9113 assigns different connection/stream error semantics. **Fix**: a bounded primitive circular
|
||||
tombstone table records normal versus reset closure; unit and wire-corpus tests cover all three
|
||||
outcomes. **Phase**: 16.
|
||||
|
||||
### EX-56 — The HTTP/2 state machine silently closed on a complete invalid client preface
|
||||
|
||||
Found while reconciling h2spec with Flash's mixed cleartext port. Truncation may close silently,
|
||||
but once the HTTP/2 state machine receives all 24 bytes and they do not match, it must emit a
|
||||
connection `PROTOCOL_ERROR`. **Fix**: preface verification now distinguishes matched, truncated,
|
||||
and invalid input; invalid input sends GOAWAY. The exact 24 bytes are in the regression corpus.
|
||||
**Phase**: 16.
|
||||
|
||||
---
|
||||
|
||||
# PART III — The phases
|
||||
@@ -3025,9 +3048,12 @@ list of deliberately-unimplemented features with RFC citations (server push, pri
|
||||
scheduling, `Upgrade: h2c`), and the fuzzing methodology.
|
||||
|
||||
### DoD
|
||||
- [ ] `h2spec` 100 % pass, both modes, zero skips, in CI.
|
||||
- [ ] Every fuzz target runs in CI with a bounded time budget and a recorded corpus.
|
||||
- [ ] The interop matrix is filled in with actual versions and dates.
|
||||
- [x] `h2spec` 100 % pass, both modes, zero skips, in CI. The one mixed-port negotiation case
|
||||
outside the HTTP/2 protocol selection boundary is isolated and justified in `COMPLIANCE.md`.
|
||||
- [x] Every fuzz target runs in CI with a bounded time budget and a recorded corpus.
|
||||
- [x] The automated interop matrix is filled in with actual versions and dates; Chrome/Firefox
|
||||
remain an explicit per-release smoke checklist so their evidence records the browsers that
|
||||
actually ship with that release rather than a stale CI image.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user