The runnable demo host and the conformance / interop test drivers for the
from-scratch HTTP/2 stack that lives in the Vanaheimr Hermod library (built
directly on SslStream — no Kestrel, no System.Net.Http HTTP/2 stack,
everything hand-rolled). Hermod is pulled in here as a git submodule under
libs/; this repository adds:
- a runnable demo host (
Demo/) on TLSh2(:8443) and cleartexth2c(:8080); - demo-driven raw-frame harnesses (
tests/) — abuse/hardening, CONNECT/ WebSocket, RFC 9218 priority scheduling, RFC 9110 semantics — driven against the live demo host; - external-conformance drivers — h2spec
(146/146 over
h2+h2c) and the Autobahn TestSuite (481/517, the other 36 declined rather than failed — see Test).
The bulk of the coverage — 619 NUnit unit + integration tests — lives with
the stack in Hermod (HermodTests/HTTP2/); see the Test section.
📖 The stack's own reference — the API, the RFC-compliance matrix, the
feature-by-feature breakdown, the security-hardening summary, and what's out of
scope — lives next to the code in libs/Hermod/Hermod/HTTP2/README.md
(GitHub: Vanaheimr/Hermod →
Hermod/HTTP2/README.md). This repo's docs/BUILD_LOG.md
holds the full chronological build history, and INTEROP.md the
interop evidence in both directions — our client against eight foreign servers,
six foreign clients against our server, including headless Chrome, Edge and
h2load — plus how to repeat any of it.
⚠️ Reference implementation — built for learning the wire protocol, not for production traffic (single-process demo host, no server push, etc.).
- .NET 10 SDK (
net10.0target)
The hand-rolled HTTP/2 stack lives in the Vanaheimr Hermod library, which —
together with Styx — is pulled in as a git submodule under libs/. Clone
with submodules, otherwise libs/Hermod and libs/Styx are empty and
nothing builds:
git clone --recurse-submodules https://github.com/Vanaheimr/HTTP2ConformanceTests.git
# already cloned without --recurse-submodules?
git submodule update --init --recursive# from the repository root — the solution lives here, not under src/:
dotnet build HTTP2.slnx
dotnet run --project Demo/HTTP2.Demo.csprojThe demo listens on https://localhost:8443 (HTTP/2 over TLS, self-signed cert
generated at startup) and additionally on http://localhost:8080 (cleartext
HTTP/2 — "h2c" — with prior knowledge, no TLS).
Most of the coverage is 619 NUnit tests under
libs/Hermod/HermodTests/HTTP2/ — the
HPACK/Huffman codec and stream state machine, plus the full in-process
integration matrix (streaming bodies + trailers, RFC 9111 caching, auth/mTLS,
timeout hardening, backpressure, the client pool/robustness, WebSocket framing,
and client interop vs. .NET Kestrel and the real gRPC client). Run them from
this solution:
dotnet test HTTP2.slnx --filter "FullyQualifiedName~Tests.HTTP2"On top of that, the tests/ folder holds the demo-driven
raw-frame scenarios (abuse/hardening, CONNECT/WebSocket, RFC 9218 priority
scheduling, RFC 9110 semantics) that run against a live demo host. Run the whole
harness suite (builds, starts the demo host, drives every scenario) with:
tests/run-tests.sh # Linux, macOS, WSL — and Windows under Git BashCurrent status: 49/49 harness runs pass on Windows and on Linux, and both
are gated per push. Getting there took closing three scenarios that used to
differ between the platforms — a real server bug (a rejected trailer block
desynced the connection's HPACK table), two priority scenarios that asserted
frame ordering without first establishing that both streams were contending,
and a PowerShell runner that passed no arguments to any harness at all, which
is why Windows had looked greener throughout. That last one is why there is now
one runner rather than a PowerShell/bash pair per driver. See
CLAUDE.md. The stack scores 146/146 on
h2spec (the canonical HTTP/2
conformance suite) over both the TLS and cleartext-h2c listeners. Reproduce
the h2spec run with a single command —
tests/h2spec.sh # builds, starts the demo, runs h2spec on both transports— see tests/TestingAgainst_h2spec.md for the
full h2spec walkthrough, tests/README.md for the harness
layout, and docs/BUILD_LOG.md for the conformance breakdown.
The WebSocket framing (RFC 6455) likewise passes 481/517 cases of the
canonical Autobahn TestSuite
— the full suite, including permessage-deflate (RFC 7692) compression —
via tests/autobahn.sh (Docker), with the critical
cases also pinned in the committed h2wsconformance harness (no Docker needed);
see tests/TestingAgainst_Autobahn.md.
Ad-hoc curl checks against the demo host:
curl --http2 -k https://localhost:8443/
curl --http2 -k https://localhost:8443/echo -d "Hello HTTP/2!"
curl --http2 -k https://localhost:8443/large # 128 KiB — exercises flow control
curl --http2 -k https://localhost:8443/slow # 2 s handler — exercises multiplexing
# RFC 9110 core mechanics — GET/HEAD/OPTIONS, conditional requests, Range:
curl --http2 -k -I https://localhost:8443/files/resource.txt # HEAD
curl --http2 -k -X OPTIONS https://localhost:8443/files/resource.txt # -> 204 + Allow
curl --http2 -k -H 'Range: bytes=0-9' https://localhost:8443/files/resource.txt
curl --http2 -k -H 'If-None-Match: "<etag from a prior response>"' https://localhost:8443/files/resource.txt
# RFC 10008 — the HTTP QUERY method (a safe, body-carrying read). /search:
curl --http2 -k https://localhost:8443/search # GET -> whole corpus
curl --http2 -k -X QUERY --data 'ap' https://localhost:8443/search # QUERY -> filtered (note Content-Location)
# RFC 9110 content negotiation — /files/greeting has en/de text + en JSON variants:
curl --http2 -k https://localhost:8443/files/greeting # server default (en text)
curl --http2 -k -H 'Accept-Language: de' https://localhost:8443/files/greeting # -> German
curl --http2 -k -H 'Accept: application/json' https://localhost:8443/files/greeting # -> JSON (note the Vary header)
# RFC 9110 §11 auth — /secret needs Basic alice:secret or Bearer valid-token-123:
curl --http2 -k -i https://localhost:8443/secret # -> 401 + WWW-Authenticate
curl --http2 -k -u alice:secret https://localhost:8443/secret # -> 200
curl --http2 -k -H 'authorization: Bearer valid-token-123' https://localhost:8443/secret # -> 200
# cleartext h2c (prior knowledge — no TLS), on :8080:
curl --http2-prior-knowledge http://localhost:8080/
curl --http2-prior-knowledge http://localhost:8080/echo -d "Hello h2c!"-k skips certificate verification (self-signed). --http2 forces HTTP/2 over
TLS via ALPN; --http2-prior-knowledge speaks cleartext HTTP/2 directly (no
Upgrade, no TLS). Note: the curl bundled with Windows has no HTTP/2 support and
silently falls back to HTTP/1.1.
Each public enum / interface / class / struct / record lives in its own file named after the type. The HTTP/2 stack itself lives inside the Hermod submodule; this repository wraps it with a runnable demo, the remaining live-host harnesses, and the solution:
HTTP2ConformanceTests/ solution HTTP2.slnx (at the repo root)
├── libs/
│ ├── Hermod/ ← git submodule (Vanaheimr Hermod)
│ │ ├── Hermod/HTTP2/ the hand-rolled HTTP/2 stack:
│ │ │ ├── Core/ direction-neutral — framing, HPACK (+ Huffman), the stream
│ │ │ │ state machine + flow control, settings, the request/streaming
│ │ │ │ seams, RFC 9110 semantics, RFC 9111 cache logic
│ │ │ ├── Server/ HTTP2Connection + HTTP2Server (TLS/ALPN, mTLS, cleartext h2c)
│ │ │ ├── Client/ HTTP2ClientConnection + HTTP2Client + caching client + pool
│ │ │ ├── WebSocket/ RFC 6455 + RFC 7692 framing over IHTTP2Tunnel
│ │ │ └── Auth/ RFC 9110 §11 framework + Basic/Bearer/Digest/Token schemes
│ │ └── HermodTests/HTTP2/ the 619 NUnit tests + shared fixtures (H2, TestH2Server,
│ │ H2Raw, MockH2Server, KestrelH2Server)
│ └── Styx/ ← git submodule (Vanaheimr Styx — Hermod's dependency)
├── Demo/ runnable demo host (→ Hermod, Styx) + example handlers
├── tests/ demo-driven raw-frame harnesses + h2spec/Autobahn drivers + h2interop + h2bench
├── tools/ browser-interop.ps1 (headless Chrome/Edge), h2load.sh (concurrency)
├── INTEROP.md interop evidence in both directions, and how to repeat it
└── docs/BUILD_LOG.md full chronological build history
For the file-by-file breakdown of the stack, see the Architecture tables in
CLAUDE.md and libs/Hermod/Hermod/HTTP2/README.md.
The hand-rolled stack itself — framing, HPACK, the stream state machine, flow
control, the server and client roles, WebSocket framing, HTTP semantics, auth,
and caching — lives in the Hermod submodule under libs/Hermod/Hermod/HTTP2/.
Its complete reference — the public API and where application logic plugs in,
the RFC-compliance matrix, the feature-by-feature breakdown, the non-standard
extensions, the security-hardening summary, what's explicitly out of scope, the
interop reference peers, and the RFC list — is documented next to the code in
libs/Hermod/Hermod/HTTP2/README.md
(on GitHub: Vanaheimr/Hermod →
Hermod/HTTP2/README.md). For the file-by-file architecture see
CLAUDE.md.
The stack is HTTP/2 feature-complete and verified end-to-end — 619 NUnit
tests + 49 live-host harness runs + h2spec 146/146 + Autobahn
481/517. The first two run per push on Windows and Debian 13
(ci.yml), the latter two nightly
(nightly.yml), which is also where the h2spec
and Autobahn numbers get re-measured rather than merely remembered.
The split between the two is deliberate. The push gate builds against the
pinned Hermod commit, so a red run always means this change broke
something; the nightly adds an against-hermod-master job that builds the
same tests against
Hermod and Styx as they are right now, which answers the question the gate
cannot — whether the pin is still safe to advance. The stack's own reference
lives in
libs/Hermod/Hermod/HTTP2/README.md;
docs/BUILD_LOG.md is the full chronological build log
(every feature, why it was built that way, and how it was verified); and
CLAUDE.md holds the architecture, conventions, and a
current-state summary (the agent/working-notes file).
Apache License 2.0 — © 2010-2026 GraphDefined GmbH. The full text is in
LICENSE, and every source file carries the standard Apache-2.0
header. The Hermod and Styx submodules are likewise Apache-2.0.