{"id":47993199,"url":"https://github.com/endel/quic-zig","last_synced_at":"2026-04-04T11:50:41.787Z","repository":{"id":344398447,"uuid":"584439490","full_name":"endel/quic-zig","owner":"endel","description":"QUIC implementation in pure Zig ⚡","archived":false,"fork":false,"pushed_at":"2026-04-03T03:10:24.000Z","size":3390,"stargazers_count":20,"open_issues_count":8,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-03T12:27:06.824Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://web-transport.dev","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/endel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-01-02T15:15:33.000Z","updated_at":"2026-04-03T00:32:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/endel/quic-zig","commit_stats":null,"previous_names":["endel/quic-zig"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/endel/quic-zig","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endel%2Fquic-zig","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endel%2Fquic-zig/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endel%2Fquic-zig/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endel%2Fquic-zig/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/endel","download_url":"https://codeload.github.com/endel/quic-zig/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/endel%2Fquic-zig/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31398770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-04T11:50:41.692Z","updated_at":"2026-04-04T11:50:41.760Z","avatar_url":"https://github.com/endel.png","language":"Zig","funding_links":["https://github.com/sponsors/endel"],"categories":[],"sub_categories":[],"readme":"![quic-zig](./quic-zig.svg)\n\nA QUIC / H3 / WebTransport implementation in pure Zig.\n\n\u003e **Current state:** 🚨 Not stable. APIs may change at any time.\n\n---\n\nCheck out my [GitHub Sponsors](https://github.com/sponsors/endel) for motivation\nand goals of this project!\n\n## Features\n\n- **QUIC v1 \u0026 v2** (RFC 9000 / RFC 9369) — handshake, streams, flow control, connection migration, PMTUD, ECN\n- **TLS 1.3** (RFC 8446 / RFC 9001) — ECDSA P-256 + RSA PSS, X25519, AES-128-GCM + ChaCha20, session resumption, 0-RTT\n- **Loss Detection \u0026 Congestion Control** (RFC 9002) — CUBIC, PTO, token bucket pacer\n- **HTTP/3** (RFC 9114) — QPACK static table, request/response, priority scheduling (RFC 9218)\n- **WebTransport** (draft-ietf-webtrans-http3) — bidi/uni streams, datagrams, Extended CONNECT, browser support\n- **HTTP/1.1+TLS** — static file server on TCP, same cert as QUIC, Alt-Svc for HTTP/3 upgrade\n\n## The Story\n\nThis project started in February 2022 with a simple UDP listener and a\nquestion: _\"Is it possible to write an entire WebTransport implementation in Zig?\"_\n\nWhat followed was months of painful, incremental progress. Parsing QUIC Initial\nheaders byte by byte. Getting stuck on packet number decryption. Falling down\nthe TLS 1.3 rabbit hole. Evaluating every crypto library under the sun\n-- BoringSSL, BearSSL, picotls, s2n -- watching pure-Zig TLS efforts like\n[feilich](https://github.com/Luukdegram/feilich) emerge and eventually TLS\nland in Zig's standard library. Reading [quiche](https://github.com/cloudflare/quiche) source\ncode for the tenth time, mesmerized by how clean it was, wondering if my own\nattempt would ever get there.\n\nBy August 2022, the reality had fully set in: _\"The more I read implementations\nand portions of the specs, the more I see this is a multi-year endeavour that\nmay never end. I'm struggling to implement the very basics.\"_ The project was\nshelved. QUIC is not one spec -- it's a stack of RFCs (9000, 9001, 9002, 9114,\n9204, 9297) each building on the last, each with enough edge cases to fill a\ncareer. For a solo developer, it was humanly impossible.\n\nFast-forward to 2026. Claude Code changed the equation. Not by writing perfect\ncode -- but by making it possible to move fast enough across the full stack that\nthe project could actually reach the point where it gets battle-tested. The\nentire codebase was rebuilt from scratch: TLS 1.3 handshake, QUIC transport,\nloss detection, congestion control, HTTP/3, QPACK, and WebTransport -- all in\npure Zig, no C dependencies.\n\nThe code passes most interop tests against [quic-go](https://github.com/quic-go/quic-go)\nand [quiche](https://github.com/cloudflare/quiche), and integrates with the\nofficial [QUIC Interop Runner](https://github.com/quic-interop/quic-interop-runner).\n\nIs AI-assisted code \"slop\"? Only until it's battle-tested. That's the challenge\n-- and I'm hoping we can get there.\n\n\n## Using as a Library\n\nAdd to your `build.zig.zon`:\n\n```bash\nzig fetch --save git+https://github.com/endel/quic-zig\n```\n\nThen in your `build.zig`:\n\n```zig\nconst quic_dep = b.dependency(\"quic\", .{ .target = target, .optimize = optimize });\n\nconst exe = b.addExecutable(.{\n    .name = \"my-app\",\n    .root_module = b.createModule(.{\n        .root_source_file = b.path(\"src/main.zig\"),\n        .target = target,\n        .optimize = optimize,\n        .imports = \u0026.{.{ .name = \"quic\", .module = quic_dep.module(\"quic\") }},\n    }),\n});\n```\n\n### High-level API (event loop server)\n\n```zig\nconst quic = @import(\"quic\");\nconst event_loop = quic.event_loop;\n\nconst MyHandler = struct {\n    pub const protocol: event_loop.Protocol = .webtransport;\n\n    pub fn onConnectRequest(_: *MyHandler, session: *event_loop.Session, session_id: u64, _: []const u8) void {\n        session.acceptSession(session_id) catch return;\n    }\n\n    pub fn onStreamData(_: *MyHandler, session: *event_loop.Session, stream_id: u64, data: []const u8, fin: bool) void {\n        if (data.len \u003e 0) {\n            session.sendStreamData(stream_id, data) catch {}; // echo\n        }\n        if (fin) session.closeStream(stream_id);\n    }\n\n    pub fn onDatagram(_: *MyHandler, session: *event_loop.Session, session_id: u64, data: []const u8) void {\n        session.sendDatagram(session_id, data) catch {}; // echo\n    }\n\n    pub fn onSessionReady(_: *MyHandler, _: *event_loop.Session, _: u64) void {}\n    pub fn onSessionClosed(_: *MyHandler, _: *event_loop.Session, _: u64, _: u32, _: []const u8) void {}\n};\n\npub fn main() !void {\n    var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n    const alloc = gpa.allocator();\n\n    var handler = MyHandler{};\n    var server = try event_loop.Server(MyHandler).init(alloc, \u0026handler, .{\n        .port = 4433,\n        .cert_path = \"cert.pem\",\n        .key_path = \"key.pem\",\n    });\n    defer server.deinit();\n    try server.run();\n}\n```\n\n### Serving static files over HTTPS (HTTP/1.1+TLS)\n\nThe server can optionally serve static files over HTTP/1.1+TLS on the same port\nalongside QUIC. TCP and UDP are separate namespaces, so the same port works for\nboth. The same TLS certificate is shared. An `Alt-Svc` header is automatically\nincluded to advertise HTTP/3 to browsers.\n\n```zig\nvar server = try event_loop.Server(MyHandler).init(alloc, \u0026handler, .{\n    .port = 4433,\n    .cert_path = \"cert.pem\",\n    .key_path = \"key.pem\",\n    .http1 = .{ .static_dir = \"public\" },\n});\n```\n\nThis is particularly useful for browser WebTransport — the browser loads the\nHTML/JS page over HTTPS, then upgrades to WebTransport over QUIC:\n\n| Transport | Port | Protocol |\n|-----------|------|----------|\n| UDP | 4433 | QUIC / H3 / WebTransport (TLS 1.3) |\n| TCP | 4433 | HTTP/1.1 static files (TLS 1.3) |\n\n`Http1Config` options:\n\n| Field | Default | Description |\n|---|---|---|\n| `static_dir` | *(required)* | Directory to serve files from |\n| `port` | same as QUIC | TCP port override |\n| `alt_svc` | `true` | Send `Alt-Svc: h3=\":port\"` header |\n\n### Graceful shutdown\n\nThe server exposes `stop()` for graceful shutdown — it sends CONNECTION_CLOSE to\nall active connections, waits for the drain period (3×PTO), then exits. Signal\nhandling is the application's responsibility:\n\n```zig\nconst std = @import(\"std\");\nconst posix = std.posix;\nconst quic = @import(\"quic\");\n\nvar server_instance: ?*MyServer = null;\n\nfn handleSignal(_: c_int) callconv(.c) void {\n    if (server_instance) |s| s.stop();\n}\n\npub fn main() !void {\n    // ...\n    var server = try quic.event_loop.Server(MyHandler).init(alloc, \u0026handler, .{ .port = 4433 });\n    defer server.deinit();\n    server_instance = \u0026server;\n\n    // Install signal handlers\n    const act = posix.Sigaction{\n        .handler = .{ .handler = handleSignal },\n        .mask = std.mem.zeroes(posix.sigset_t),\n        .flags = 0,\n    };\n    posix.sigaction(posix.SIG.TERM, \u0026act, null);\n    posix.sigaction(posix.SIG.INT, \u0026act, null);\n\n    try server.run(); // blocks until stop() is called and all connections drain\n}\n```\n\n### High-level API (event loop client)\n\nThe client mirrors the server pattern — define a handler struct, and `Client(Handler)` manages the QUIC handshake, H3/WebTransport setup, and Extended CONNECT automatically:\n\n```zig\nconst quic = @import(\"quic\");\nconst event_loop = quic.event_loop;\n\nconst MyHandler = struct {\n    pub const protocol: event_loop.Protocol = .webtransport;\n\n    pub fn onSessionReady(_: *MyHandler, session: *event_loop.ClientSession, session_id: u64) void {\n        const stream_id = session.openBidiStream(session_id) catch return;\n        session.sendStreamData(stream_id, \"Hello!\") catch {};\n        session.closeStream(stream_id);\n\n        session.sendDatagram(session_id, \"Hello via datagram!\") catch {};\n    }\n\n    pub fn onStreamData(_: *MyHandler, session: *event_loop.ClientSession, stream_id: u64, data: []const u8, _: bool) void {\n        if (data.len == 0) return;\n        std.debug.print(\"Response on stream {d}: {s}\\n\", .{ stream_id, data });\n        session.closeConnection();\n    }\n\n    pub fn onDatagram(_: *MyHandler, session: *event_loop.ClientSession, session_id: u64, data: []const u8) void {\n        std.debug.print(\"Datagram: {s}\\n\", .{data});\n        _ = session_id;\n    }\n};\n\npub fn main() !void {\n    var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n    const alloc = gpa.allocator();\n\n    var handler = MyHandler{};\n    var client = try event_loop.Client(MyHandler).init(alloc, \u0026handler, .{\n        .address = \"127.0.0.1\",\n        .port = 4433,\n        .server_name = \"localhost\",\n        .ca_cert_path = \"ca.crt\",\n    });\n    defer client.deinit();\n    try client.run();\n}\n```\n\n`ClientConfig` options:\n\n| Field | Default | Description |\n|---|---|---|\n| `address` | `\"127.0.0.1\"` | Server IP address |\n| `port` | `4433` | Server port |\n| `server_name` | `\"localhost\"` | TLS SNI / CONNECT authority |\n| `path` | `\"/.well-known/webtransport\"` | WebTransport CONNECT path |\n| `ca_cert_path` | `null` | CA certificate for TLS verification |\n| `skip_cert_verify` | `false` | Skip certificate verification (testing only) |\n| `max_datagram_frame_size` | `65536` | QUIC datagram frame size limit |\n| `ipv6` | `false` | Use IPv6 dual-stack socket |\n| `tls_config` | `null` | Override TLS config directly |\n| `conn_config` | `null` | Override QUIC connection config |\n\nHandler callbacks (all optional):\n\n| Callback | Description |\n|---|---|\n| `onConnected(session)` | QUIC handshake complete |\n| `onSessionReady(session, session_id)` | WebTransport session established |\n| `onSessionRejected(session, session_id, status)` | Server rejected CONNECT |\n| `onStreamData(session, stream_id, data[, fin])` | Data received on a stream |\n| `onDatagram(session, session_id, data)` | Datagram received |\n| `onBidiStream(session, session_id, stream_id)` | Incoming bidi stream opened |\n| `onUniStream(session, session_id, stream_id)` | Incoming uni stream opened |\n| `onSessionClosed(session, session_id, error_code, reason)` | Session closed |\n| `onSessionDraining(session, session_id)` | Session draining |\n| `onPollComplete(session)` | Called each poll cycle |\n\n### Low-level API (direct connection control)\n\n```zig\nconst quic = @import(\"quic\");\n\n// Client\nvar conn = try quic.connection.connect(allocator, \"example.com\", .{}, tls_config, null);\ndefer conn.deinit();\n\n// Send/receive loop\nvar out: [1500]u8 = undefined;\nconst n = conn.send(\u0026out) catch 0;\n// sendto(sockfd, out[0..n], ...)\n// recvfrom(...) -\u003e buf\nconn.handleDatagram(buf[0..len], recv_info);\n```\n\n## Building\n\nRequires **Zig 0.15.2**.\n\n```bash\nzig build\n```\n\nProduces binaries in `zig-out/bin/`:\n\n| Binary | Description |\n|--------|-------------|\n| `server` | HTTP/3 echo server (127.0.0.1:4434) |\n| `client` | HTTP/3 client |\n| `wt-server` | WebTransport echo server |\n| `wt-client` | WebTransport client |\n| `wt-browser-server` | WebTransport server for browser clients (0.0.0.0:4433) |\n| `interop-server` | QUIC Interop Runner server endpoint |\n| `interop-client` | QUIC Interop Runner client endpoint |\n| `interop-wt-server` | QUIC Interop Runner WebTransport server |\n\n## Running Tests\n\n```bash\nzig build test\n```\n\n## Interop Testing\n\n### Local\n\nBidirectional interop is verified against [quic-go](https://github.com/quic-go/quic-go) and [quiche](https://github.com/cloudflare/quiche) across QUIC, HTTP/3, and WebTransport. Browser WebTransport (Chrome) is also tested.\n\nAn automated test script covers all combinations:\n\n```bash\n./interop/run_local_tests.sh\n```\n\nOr run individual tests manually:\n\n```bash\n# Build Go interop programs\ncd interop/quic-go\ngo build -o h3server_bin ./h3server \u0026\u0026 go build -o h3client_bin ./h3client\ngo build -o wt_server_bin ./wt_server \u0026\u0026 go build -o wt_client_bin ./wt_client\n\n# H3: Zig server ↔ Go client\nzig-out/bin/server \u0026\n./interop/quic-go/h3client_bin --addr localhost:4434\n\n# WebTransport: Zig server ↔ Go client\nzig-out/bin/wt-server \u0026\n./interop/quic-go/wt_client_bin --addr localhost:4434\n\n# Browser WebTransport (requires ECDSA cert)\ncd interop/browser \u0026\u0026 ./generate-cert.sh\nzig build run-wt-browser-server\n# Open interop/browser/index.html in Chrome\n```\n\n### QUIC Interop Runner\n\nThis project integrates with the official [QUIC Interop Runner](https://github.com/quic-interop/quic-interop-runner), the framework used by all major QUIC implementations for cross-implementation testing.\n\n**Prerequisites:**\n\n- Docker (with `docker compose` v2)\n- Python 3\n- [Wireshark](https://www.wireshark.org/) \u003e= 4.5.0 (`tshark` must be in PATH)\n\n**Setup:**\n\n```bash\n# Initialize the interop runner submodule\ngit submodule update --init interop/quic-interop-runner\n\n# Install Python dependencies\npip3 install -r interop/quic-interop-runner/requirements.txt\n```\n\n**Run tests:**\n\n```bash\n# Handshake test (quic-zig ↔ quic-zig)\n./interop/runner/run.sh handshake\n\n# Multiple tests\n./interop/runner/run.sh handshake,transfer,retry\n\n# Test against another implementation (e.g. quic-go)\n./interop/runner/run.sh handshake quic-go\n```\n\nThe script builds a Docker image (`quic-zig-interop:latest`), injects it into the runner's implementation list, and executes the tests.\n\n**Manual Docker build:**\n\n```bash\ndocker build --platform linux/amd64 \\\n  -t quic-zig-interop:latest \\\n  -f interop/runner/Dockerfile .\n```\n\n## License\n\nMIT License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendel%2Fquic-zig","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fendel%2Fquic-zig","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fendel%2Fquic-zig/lists"}