198 lines
13 KiB
Markdown
198 lines
13 KiB
Markdown
# 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](#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 pool–based 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
|
||
```cpp
|
||
// Create a TCP client
|
||
Crafter::ClientTCP client("localhost", 8080);
|
||
client.Send("Hello World", 11);
|
||
|
||
// Receive data
|
||
std::vector<char> data = client.RecieveSync();
|
||
```
|
||
|
||
#### ListenerTCP
|
||
```cpp
|
||
// 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
|
||
```cpp
|
||
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
|
||
```cpp
|
||
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.
|
||
|
||
```cpp
|
||
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:
|
||
|
||
```cpp
|
||
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](https://forgejo.catcrafts.net/Catcrafts/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/](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
|
||
|
||
```cpp
|
||
#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](LICENSE) for more information.
|
||
|
||
## Copyright
|
||
|
||
Copyright (C) 2026 Catcrafts®
|
||
Catcrafts.net
|