C++ multipurpose networking library for linux
  • C++ 94.6%
  • JavaScript 5.4%
Find a file
Jorijn van der Graaf 43fdd7fb53 fix(listener): stop dropping a peer's streams that arrive during connection setup
ListenerQUIC installed only a no-op bootstrap connection callback in the
NEW_CONNECTION handler and deferred the real ClientQUIC callback to the
ThreadPool, alongside per-connection onConnect setup. An HTTP/3 peer (notably
Chromium) opens its control + QPACK + request streams the instant the QUIC
handshake completes — potentially before that deferred task ran. Those early
PEER_STREAM_STARTED events were delivered to the bootstrap and silently
dropped, so the session never completed. Over the network this surfaced as an
intermittent "WebTransport connection rejected" that cleared on retry.

Construct the ClientQUIC (and thus install its real connection callback)
synchronously inside NEW_CONNECTION, before the handler returns and before
msquic delivers any further events. pendingAccepted now holds the constructed
ClientQUIC*; the accept loops just dispatch it, and the destructor cleans up
any peer accepted but never dispatched.

Also park WT data streams that arrive before their CONNECT session is
registered (the stream demux races the CONNECT handler) and drain them on
registration, instead of dropping them.

Tests:
- New ShouldNotDropEarlyStreams reproduces the race deterministically by
  saturating the ThreadPool so onConnect is gated while the client opens its
  request stream; fails on the pre-fix build, passes after.
- Give ShouldEchoWebTransport its own port (8085) so it no longer collides
  with ShouldSendRecieveKeepaliveHTTP (8083) under the parallel test runner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 16:56:07 +02:00
additional browser wasm 2026-05-19 02:53:50 +02:00
examples/SimpleClient browser wasm 2026-05-19 02:53:50 +02:00
implementations fix(listener): stop dropping a peer's streams that arrive during connection setup 2026-05-29 16:56:07 +02:00
interfaces browser wasm 2026-05-19 02:53:50 +02:00
tests fix(listener): stop dropping a peer's streams that arrive during connection setup 2026-05-29 16:56:07 +02:00
.gitignore initial commit 2025-11-02 15:00:53 +01:00
LICENSE initial commit 2025-11-02 15:00:53 +01:00
project.cpp fix(listener): stop dropping a peer's streams that arrive during connection setup 2026-05-29 16:56:07 +02:00
README.md browser wasm 2026-05-19 02:53:50 +02:00

Crafter.Network

A cross-platform C++ networking library providing TCP, QUIC, HTTP/3, and WebTransport client/server functionality with modern C++ features. Builds for native Linux and for the browser (wasm32-wasip1).

Overview

Crafter.Network is a C++ networking library designed for modern C++ applications. It provides TCP, QUIC, HTTP/3, and WebTransport-over-HTTP/3 capabilities with support for synchronous and asynchronous operations, making it suitable for a wide range of networking tasks including real-time multiplayer games. The same source compiles for native Linux (via msquic + POSIX sockets) and for the browser (via fetch() + WebTransport JS APIs); see Browser build.

Features

  • TCP Networking: Client and server implementations for raw TCP connections (native only).
  • QUIC Networking: Encrypted, multi-stream transport via msquic — reliable streams for control plane, unreliable datagrams for low-latency state sync.
  • HTTP/3: Client and server implementations on top of QUIC. Uses ALPN h3, QUIC bidi streams for requests/responses, the mandatory unidirectional control stream + SETTINGS frame (RFC 9114 §6.2.1), the (empty) QPACK encoder + decoder unidi streams required by stricter peers like Chromium, and a built-in QPACK codec (RFC 9204) with the full static table, Huffman decoding (RFC 7541), and literal-only emission. The QPACK dynamic table is unused. The client is interoperable with mainstream public h3 endpoints (cloudflare, nghttp3-based servers, etc.).
  • WebTransport (server): ListenerHTTP accepts extended-CONNECT sessions (:method=CONNECT, :protocol=webtransport) negotiated on the existing h3 listener — no separate port or alternate stack. Both draft-02 and draft-07+ identifier sets are advertised in SETTINGS so current Chrome/Edge browsers connect out of the box. Per-route handlers receive a WebTransportSession& and can multiplex bidirectional streams over the session.
  • Browser client: Same C++ API compiled to wasm32-wasip1 and routed through fetch() (for ClientHTTP) and WebTransport (for ClientQUIC). Listeners and raw TCP are not compiled in the browser build — the browser is client-only.
  • Asynchronous Operations: Thread poolbased async operations on native; the same *Async API on the browser side, where it's required (no synchronous I/O in the browser event loop).
  • Cross-Platform: Native Linux (POSIX sockets + msquic) and browser (wasm32-wasip1).
  • Modern C++: Uses C++20 modules, STL containers, and modern C++ features.

Architecture

The library follows a modular design using C++20 modules:

Core Modules

  • Crafter.Network: Main module that exports all components
  • Crafter.Network:ClientTCP: TCP client implementation (native only)
  • Crafter.Network:ListenerTCP: TCP server implementation (native only)
  • Crafter.Network:ClientHTTP: HTTP/3 client (ALPN h3). On browser builds this maps to fetch().
  • Crafter.Network:ListenerHTTP: HTTP/3 + WebTransport server (ALPN h3, native only)
  • Crafter.Network:HTTP: HTTP request/response types and constructors
  • Crafter.Network:ClientQUIC: QUIC connection (client + accepted-server side) with reliable streams and unreliable datagrams. On browser builds this maps to the WebTransport JS API.
  • Crafter.Network:ListenerQUIC: QUIC listener accepting incoming connections (native only). Also exports ComputeCertificateHashSHA256() and GetSelfSignedCertificatePath() for browser-peer cert pinning.
  • Crafter.Network:WebTransport: WebTransportSession type — the per-session handle handed to ListenerHTTP WT route handlers.
  • Crafter.Network:HTTP3: HTTP/3 wire-format helpers (QUIC varint, frame layer, QPACK static-table codec, WT frame/setting constants). Re-exported on native, excluded on browser (it uses throw and the wasm build is -fno-exceptions).

Components

TCP Components

ClientTCP

// Create a TCP client
Crafter::ClientTCP client("localhost", 8080);
client.Send("Hello World", 11);

// Receive data
std::vector<char> data = client.RecieveSync();

ListenerTCP

// Create a TCP listener
auto callback = [](Crafter::ClientTCP* client) {
    // Handle new connection
};
Crafter::ListenerTCP listener(8080, callback);
listener.ListenSyncSync(); // Synchronous listening

HTTP/3 Components

HTTP/3 runs over QUIC, which always requires TLS. Pass server credentials when constructing the listener (or set selfSigned = true for a development-only ephemeral cert) and matching client credentials when constructing the client (insecureNoServerValidation = true for self-signed servers).

ClientHTTP

Crafter::QUICClientCredentials creds;
creds.insecureNoServerValidation = true; // dev-only
Crafter::ClientHTTP client("localhost", 8082, creds);

Crafter::HTTPResponse response = client.Send(
    Crafter::CreateRequestHTTP("GET", "/", "localhost")
);
// response.status is the numeric status as a string, e.g. "200"

ListenerHTTP

std::unordered_map<std::string,
    std::function<Crafter::HTTPResponse(const Crafter::HTTPRequest&)>> routes;
routes["/hello"] = [](const Crafter::HTTPRequest&) {
    return Crafter::CreateResponseHTTP("200", "Hello World!");
};

Crafter::QUICServerCredentials creds;
creds.selfSigned = true; // dev-only
Crafter::ListenerHTTP listener(8082, creds, routes);
listener.Listen();

The HTTPRequest exposes the four HTTP/3 pseudo-headers (method, scheme, authority, path) as named struct fields rather than mixing them into the regular headers map. Routes are dispatched by exact match on path; unmatched paths return a synthetic 404.

WebTransport Components

ListenerHTTP has a WT-aware constructor overload that takes a second route map keyed by :path. When the map is non-empty the listener advertises both draft-02 (SETTINGS_ENABLE_WEBTRANSPORT = 0x2b603742) and draft-07+ (SETTINGS_WT_MAX_SESSIONS = 0xc671706a) identifiers in its SETTINGS frame so current browsers connect. An extended-CONNECT request (:method=CONNECT, :protocol=webtransport) whose :path matches a registered route is accepted with a 200 (no FIN), upgraded into a WebTransportSession, and dispatched on the ThreadPool. Plain HTTP/3 routes and WT routes coexist on the same listener and port.

std::unordered_map<std::string,
    std::function<Crafter::HTTPResponse(const Crafter::HTTPRequest&)>> httpRoutes;
std::unordered_map<std::string,
    std::function<void(Crafter::WebTransportSession&)>> wtRoutes;

wtRoutes["/echo"] = [](Crafter::WebTransportSession& session) {
    session.OnStream([](Crafter::QUICStream peerStream) {
        auto bytes = peerStream.RecieveUntilCloseSync();
        peerStream.SendSync(bytes.data(),
                            static_cast<std::uint32_t>(bytes.size()),
                            /*finish=*/true);
    });
};

Crafter::QUICServerCredentials creds;
creds.selfSigned = true; // dev-only, see "Self-signed certs & browser peers"
Crafter::ListenerAsyncHTTP listener(4443, creds, std::move(httpRoutes), std::move(wtRoutes));

Browser-initiated bidi/unidi WebTransport streams arrive via session.OnStream(...). The WT_BIDI (0x41) / WT_UNIDI (0x54) stream-type prefix and session-id varint are stripped before the user handler sees the stream; what the handler reads is pure session payload. Phase 1 covers session establishment + bidirectional streams; WebTransport datagrams and capsule-protocol control are stubbed for a follow-up.

Self-signed certs & browser peers

Passing QUICServerCredentials{selfSigned = true} makes the listener generate (and cache) an ephemeral cert at /tmp/crafter-network-quic-cert/{cert,key}.pem and reuse it across server restarts while it's still valid. The cert is shaped to satisfy Chromium's WebTransport.serverCertificateHashes rules: ECDSA P-256, signed with ECDSA-SHA256, validity ≤14 days (10 in practice), BasicConstraints CA:FALSE, KeyUsage digitalSignature, EKU serverAuth, SAN=DNS:localhost,IP:127.0.0.1,IP:::1. To let a browser peer pin this cert without trusting a CA:

auto certPath = Crafter::GetSelfSignedCertificatePath();
auto hash     = Crafter::ComputeCertificateHashSHA256(certPath); // 32-byte SHA-256 of cert DER
// Publish hash to the browser (e.g. write hex to a file the page can fetch);
// on the browser side feed it into QUICClientCredentials::serverCertificateHash.

For production use a real cert (certPath + keyPath on QUICServerCredentials).

Browser build

Crafter.Network compiles for wasm32-wasip1 via Crafter.Build; the build path is selected automatically when cfg.target.find("wasm") != npos. On that target:

  • CRAFTER_NETWORK_BROWSER is defined. Synchronous methods on ClientHTTP / QUICStream / ClientQUIC are not compiled — only the *Async variants are available.
  • ClientHTTP calls into crafterNetworkFetch (JS) which delegates to fetch(). An empty host is a same-origin sentinel: the path is passed through as the URL, so ClientHTTP("", 0).SendAsync({.path="/data.json"}, ...) fetches from the page origin.
  • ClientQUIC calls into crafterNetworkWtConnect which constructs a WebTransport(url, opts) against https://{host}:{port}/{alpn} (i.e. alpn is the WebTransport URL path on this target). QUICClientCredentials::serverCertificateHash is forwarded as serverCertificateHashes; leaving it zeroed makes the browser fall back to its normal trust store.
  • ListenerTCP, ListenerHTTP, ListenerQUIC, ClientTCP, and the sync receive/send paths are excluded — the browser is client-only.
  • additional/network-env.js is shipped alongside the produced .wasm and merged into the runtime's env import object by EnableWasiBrowserRuntime.

A worked example pairing a wasm browser client with a native server lives in examples/SimpleClient/. Build the server with crafter-build --target=x86_64-pc-linux-gnu, run it, then run crafter-build (no --target) to produce the wasm and serve it over HTTPS.

Build Configuration

The project is a single Crafter.Build configuration (crafter-network, ConfigurationType::LibraryStatic). Target selection and debug flags are handled by ApplyStandardArgs:

  • crafter-build — host native (x86_64-pc-linux-gnu by default), msquic + listeners + sync APIs.
  • crafter-build --target=wasm32-wasip1 — browser build, fetch + WebTransport, async-only API; defines CRAFTER_NETWORK_BROWSER, drops msquic.
  • crafter-build test [globs] — build and run tests under tests/.

Testing

The library includes tests covering:

  • HTTP/3 round-trip (ShouldSendRecieveHTTP) — canonical local client/server round-trip
  • HTTP/3 connection multiplexing (ShouldSendRecieveKeepaliveHTTP) — two requests share one QUIC connection
  • HTTP/3 large body transfer (ShouldSendRecieveLargeHTTP) — 10 MiB POST
  • HTTP/3 external interop (ShouldSend) — live fetch from cloudflare-quic.com:443, exercises real TLS chain validation, mandatory control stream, peer-initiated unidi streams, and QPACK Huffman decoding
  • QUIC reliable streams (ShouldSendRecieveQUICStream)
  • QUIC unreliable datagrams (ShouldSendRecieveQUICDatagram)
  • WebTransport echo (ShouldEchoWebTransport) — extended-CONNECT acceptance, draft-02 SETTINGS, bidi data stream framing (WT_STREAM 0x41 + session-id varint), and byte-for-byte echo

The external-interop test requires outbound UDP/443; if your network blocks it the test will fail.

Dependencies

  • Crafter.Thread: Thread pool management for asynchronous operations.
  • msquic (native target only) — fetched and built automatically as a Crafter ExternalDependency (no system install required). The build clones microsoft/msquic recursively into the per-project external cache, configures it via CMake (QUIC_TLS_LIB=quictls, tests/tools/perf disabled), and links the produced libmsquic into the QUIC and HTTP/3 modules. Skipped entirely on browser builds.
    • On Linux msquic links against libnuma (provided by the numactl package on most distros).
  • The self-signed-cert path used by tests/dev shells out to the openssl CLI; install openssl if you intend to use QUICServerCredentials{selfSigned = true}. The same path also produces the cert hash that browser peers need for serverCertificateHashes.
  • Browser build has no extra dependencies beyond Crafter.Build's wasi-browser runtime: HTTP delegates to the browser's fetch(), QUIC to its WebTransport. The JS glue lives in additional/network-env.js and is shipped alongside the produced .wasm.

Usage Example

#include <Crafter.Network>
#include <iostream>

int main() {
    Crafter::QUICClientCredentials creds;
    creds.insecureNoServerValidation = true;
    Crafter::ClientHTTP client("localhost", 8443, creds);

    auto response = client.Send(Crafter::CreateRequestHTTP("GET", "/", "localhost"));

    std::cout << "Status: " << response.status << std::endl;
    std::cout << "Body: " << response.body << std::endl;
    return 0;
}

License

This library is licensed under the GNU Lesser General Public License version 3.0. See LICENSE for more information.

Copyright (C) 2026 Catcrafts® Catcrafts.net