SocketErrorException is now a std::system_error: code() carries the errno
under std::generic_category(), or getaddrinfo's result under the new
ResolverCategory(), whose message() is gai_strerror's text; `phase` says
which step failed (resolve, open, connect, send, receive). It is still a
std::runtime_error with the message ClientTCP always threw, the code's
text after it, so existing handlers keep working.
Before, the exception carried a `kind` and an int, and a caller that had
to print the cause its own way still had to match the message prefix to
learn where the failure happened. The standard type ends both: the
category knows how to print the number, and the phase is a field. A
caller writes "[Errno {}] {}" from code().value() and code().message(),
or compares code() with std::errc::connection_refused.
Breaking: SocketErrorException's `kind` and `code` members are gone;
read code().category() and code().value().
136 lines
27 KiB
Markdown
136 lines
27 KiB
Markdown
# AGENTS.md
|
|
|
|
Guidance for coding agents and contributors working on Crafter.Network itself. The [README](README.md) is the user-facing manual, and [AGENTS.consumer.md](AGENTS.consumer.md) is what agents working in a *consumer* of this library read: crafter-build copies it into the dependency-notes section of the consumer's `AGENTS.md` on every build. When any of the three disagrees with the code, the code wins; the interfaces under `interfaces/` are the contract.
|
|
|
|
## Project overview
|
|
|
|
Crafter.Network is the Crafter engine's networking library: one C++ module, `Crafter.Network`, with fourteen partitions behind its primary interface (fifteen interface files), with TCP, a byte-stream seam and a libssl TLS transport, HTTP/1.1 (client, listener and a transport-free codec), QUIC over msquic, HTTP/3 (client, listener and a wire-format partition) and WebTransport on the HTTP/3 listener. One source tree builds three libraries: native Linux (everything), native with `--no-quic` (TCP, TLS and HTTP/1.1, no msquic), and the browser (`wasm32-*`: the client half of HTTP and WebTransport over `fetch()` and the `WebTransport` API, `*Async` only). The only crafter-build dependency is Crafter.Thread, whose `ThreadPool` runs every callback and `*Async` call; msquic is an `ExternalDependency` built with CMake, and OpenSSL 3 is a system package.
|
|
|
|
| Path | What lives there |
|
|
|---|---|
|
|
| `interfaces/Crafter.Network.cppm` | The umbrella: `export import`s every partition, under `#ifndef CRAFTER_NETWORK_NO_QUIC` and `#ifndef CRAFTER_NETWORK_BROWSER` for the ones a build leaves out. |
|
|
| `interfaces/Crafter.Network-<Partition>.cppm` | One partition per file. The native-only ones wrap their whole body in `#ifndef CRAFTER_NETWORK_BROWSER`, so the browser build compiles them as empty modules. |
|
|
| `implementations/Crafter.Network-<Partition>.cpp` | The native implementation units, `Crafter.Network:<Partition>_impl`. `HTTP`, `HTTP1` and `HTTP3` have none: they are inline. |
|
|
| `implementations/Crafter.Network-ClientHTTP-Browser.cpp`, `-ClientQUIC-Browser.cpp` | The browser implementations of those two partitions, over the JS bridge. |
|
|
| `additional/network-env.js` | The JS bridge: `fetch()` and `WebTransport` behind the `env` imports the wasm declares, shipped next to a consumer's `.wasm` through `cfg.files`. |
|
|
| `project.cpp` | The build description: one static library, `crafter-network`, in its three variants, the msquic external, the OpenSSL link flags and the test declarations. |
|
|
| `tests/<Name>/main.cpp` | One test program per directory, declared in `project.cpp`. |
|
|
| `examples/SimpleClient/` | A wasm browser client and a native WebTransport echo server in one `project.cpp`, MIT licensed, meant to be copied. |
|
|
| `AGENTS.template.md`, `AGENTS.consumer.md` | The source of this file, and the notes for consumers. Every build and test run writes `AGENTS.md` from the template, with the `AGENTS.consumer.md` of each dependency that ships one merged in, and only when the text changes (`test --list`, `lint`, `format`, `run` and `clean` leave it alone); `AGENTS.md` is gitignored, so edit the template. Crafter.Thread ships none today, so there is no notes section, and a fresh clone has no `AGENTS.md` until its first build, when it gets the template under crafter-build's header line. |
|
|
| `build/`, `bin/` | Generated and gitignored, one directory per variant and per test; `build/test-logs/` keeps the output of failed tests. The example's `bin/` and `build/` match the same patterns. |
|
|
|
|
Partitions and what they own:
|
|
|
|
- `:ClientTCP`, `:ListenerTCP`: POSIX sockets, IPv4 and IPv6. `SocketClosedException`, `SocketTimeoutException`, `SocketErrorException` (a `std::system_error` with the `SocketPhase` that failed; `ResolverCategory()` for `getaddrinfo`'s codes), the receive semantics (what arrived, never a padded buffer) and the `onError` of the async receives. The listener hands its callback a `new ClientTCP` the callback owns.
|
|
- `:Stream`: `ByteStream` (`ReadSome` and `Write` with a timeout per call, `Shutdown`, `Descriptor`, `Protocol`, `Secure`) and `PlainStream`, so one HTTP/1.1 implementation serves `http://` and `https://`. `SetNonBlocking`, `PollDescriptor`.
|
|
- `:TLS`: `TLSContext`, `TLSStream`, both credential structs, `GetSelfSignedCertificatePem`. OpenSSL stays behind the `Impl` pointers; no OpenSSL header reaches an importer.
|
|
- `:HTTP`: `HTTPRequest`, `HTTPResponse`, the `Create*` helpers, `PathWithoutQueryHTTP`. Shared by both HTTP versions and by every build.
|
|
- `:HTTP1`: the RFC 9112 codec, inline: `SerializeRequest`, `SerializeResponse`, `SerializeContinue`, `MessageParser`, `MessageLimits`, `HTTP1ProtocolError`, and the framing it refuses to guess at.
|
|
- `:ClientHTTP1`, `:ListenerHTTP1`: HTTP/1.1 over a `ByteStream&`. The listener is a `ListenerTCP` with a thread per accepted connection; the client keeps one pooled connection with a replay-once rule.
|
|
- `:ClientQUIC`: `ClientQUIC` and `QUICStream` for both ends of a connection, `QUICSettings`, both QUIC credential structs, `QUICException`, `QUICClosedException`. The native unit holds the process-wide `MsQuicRuntime` (API table and registration, closed by a static destructor at exit), the stream registry with its refcounted handles, and the drain in `~ClientQUIC`.
|
|
- `:ListenerQUIC`: the msquic listener, its four `Listen*` variants, the self-signed certificate (made by the `openssl` CLI and cached under `/tmp/crafter-network-quic-cert/`), `ComputeCertificateHashSHA256`, `GetSelfSignedCertificatePath`.
|
|
- `:HTTP3`: QUIC varints, HTTP/3 frames, the QPACK static table, Huffman decoding, the control-stream preludes and the WebTransport stream prefixes. Native only: it throws for protocol errors.
|
|
- `:ClientHTTP`, `:ListenerHTTP`: HTTP/3 on `:ClientQUIC` and `:ListenerQUIC`. The listener demuxes every bidirectional stream into an HTTP/3 request or a WebTransport data stream and accepts extended CONNECT when `wtRoutes` is non-empty.
|
|
- `:WebTransport`: `WebTransportSession`, the per-session handle a WT handler gets. Bidirectional streams and session close today; datagrams and capsules are stubs.
|
|
|
|
The variants are decided in `project.cpp`. Any `wasm32-*` target defines `CRAFTER_NETWORK_BROWSER`, compiles nine interfaces (the umbrella, `ClientTCP`, `ListenerTCP`, `ClientHTTP`, `ListenerHTTP`, `HTTP`, `ClientQUIC`, `ListenerQUIC`, `WebTransport`; all but the umbrella, `HTTP`, `ClientHTTP` and `ClientQUIC` empty) with the two `-Browser` units, ships `network-env.js`, and returns before msquic or OpenSSL come up. `--no-quic` defines `CRAFTER_NETWORK_NO_QUIC`, drops the six QUIC-side interfaces and their five units, and leaves msquic out, which is most of a fresh build. The flag is this project's own and is stripped from the args passed on to Crafter.Thread, so a consumer and this project load one Crafter.Thread rather than two with different args.
|
|
|
|
## Dev environment
|
|
|
|
crafter-build is the only tool, from its Flatpak (`alias crafter-build='flatpak run net.catcrafts.CrafterBuild'`; this machine has its `stable` and `beta` branches, and a build installed from a Crafter.Build checkout is branch `local`, chosen with `--branch=`). The host is Linux x86_64. A native build needs network access to `forgejo.catcrafts.net` for Crafter.Thread, which is unpinned and `git pull`ed by every verb that loads `project.cpp`, `test --list`, `lint` and `format` included (`--local` takes `../Crafter.Thread` instead, and is forwarded so Crafter.Thread resolves the same way), and to `github.com` once for msquic, pinned to v2.6.3 (commit `16ca4d8d`) with its submodules and built by CMake into `~/.var/app/net.catcrafts.CrafterBuild/cache/crafter.build/external/msquic-<hash>/` (a native crafter-build uses `~/.cache/crafter.build/`): several minutes the first time, then shared by every project with the same spec. msquic is configured with `QUIC_TLS_LIB=openssl` against the OpenSSL the sandbox provides, 3.5 or newer (the Flatpak runtime has 3.5.9), as a shared `libmsquic.so` that the test binaries and consumers find through an absolute runpath into that cache (a directory named after msquic's spec hash, which `crafter-build clean` never touches). msquic links `libnuma` when the build host has it: a native crafter-build on Arch does, the Flatpak does not.
|
|
|
|
What the suite needs beyond that: the `openssl` CLI (the QUIC self-signed certificate and its hash; the TLS partition makes its own certificate in-process), `curl` and `python3` for the interop tests, and outbound UDP/443 to `cloudflare-quic.com` for `ShouldSend`. The Flatpak sandbox has `openssl`, `curl`, `python3` and `wasmtime` and no `node`; its `/tmp` is a per-app directory under `$XDG_RUNTIME_DIR/.flatpak/net.catcrafts.CrafterBuild/tmp`, shared by instances running at the same time and discarded once none is, so the QUIC certificate cache under `/tmp` rarely survives between runs; and it sees the host filesystem but not the host's `PATH` (`/run/host/usr/bin` is the host's `/usr/bin`). The interop tests print `skipping the ... half` and pass when a tool is missing: a pass that tested less, so read the log.
|
|
|
|
Build outputs go to `bin/crafter-network-<target>-<march>-<mtune>-<hash>/` (the PCMs, `std.pcm` and `libcrafter-network.a`; the wasm variant also carries `network-env.js`) and `build/<the same>/`. The hash covers the variant, so the default build, `--no-quic` and the browser build keep separate directories, and `bin/` also holds one directory per test. The Flatpak's host triple is `x86_64-unknown-linux-gnu`; the README's `x86_64-pc-linux-gnu` default is a native crafter-build's, and `--target=` with either is the same build in another directory. `crafter-build clean` removes them all.
|
|
|
|
## Build, test and style commands
|
|
|
|
```bash
|
|
crafter-build # native: TCP, TLS, HTTP/1.1, QUIC, HTTP/3, WebTransport
|
|
crafter-build --no-quic # native without QUIC, HTTP/3, WebTransport and msquic
|
|
crafter-build --local # Crafter.Thread from ../Crafter.Thread
|
|
crafter-build --target=wasm32-wasip1 # the browser library
|
|
crafter-build test # the suite, native: 23 tests
|
|
crafter-build test 'Should*HTTP1' # a name glob
|
|
crafter-build test --no-quic # the TCP, TLS and HTTP/1.1 tests plus ShouldBuildWithoutQUIC: 12
|
|
crafter-build test --list
|
|
crafter-build test --jobs=1 --timeout=120
|
|
crafter-build clean
|
|
```
|
|
|
|
A `--flag` crafter-build does not know is a project arg under any verb, so `--no-quic` and `--local` go anywhere before a `--`; under `test`, a bare word and everything after `--` are name globs, so `crafter-build test -- --no-quic` runs nothing. `project.cpp` registers no lint rules, so `crafter-build lint` and `format` exit 1 with "no rules"; style is checked by eye (see "Code style"). There is no CI. Before a change is done, `crafter-build test` and `crafter-build test --no-quic` pass, and `crafter-build --target=wasm32-wasip1` still builds when a `#ifndef CRAFTER_NETWORK_BROWSER` block, a browser unit or `network-env.js` was touched. Failed, crashed and timed-out tests write `build/test-logs/<Name>.log`; a later passing run does not delete them, so check the timestamp before trusting one.
|
|
|
|
## Testing instructions
|
|
|
|
Tests are declared, not discovered: one `cfg.AddTest("<Name>").Dependencies({ &cfg })` line in `project.cpp`, inside the block for Linux targets (the browser build runs no tests), in the `if (quic)` branch when the test needs msquic, or in its `else` when it only exists to show msquic stayed out (`ShouldBuildWithoutQUIC`). The source is `tests/<Name>/main.cpp`, linked against the local `libcrafter-network.a` with the variant's defines. A test is an `int main()`: 0 passes, nonzero fails, 77 skips (unused here; the interop tests skip a half by printing and passing). The runner gives each 60 s in its own process, in parallel.
|
|
|
|
Conventions the tests follow:
|
|
|
|
- Names are `Should<Verb><What>`; the `SendRecieve*` and `Interop*` families end in `HTTP1` or `HTTPS1` on the HTTP/1.1 side and `HTTP` or `QUIC` on the QUIC side, and `ShouldSend`, `ShouldNotDropEarlyStreams`, `ShouldFallbackUnknownRoutes` and `ShouldSurviveConnectionChurn` carry no suffix. Open `main.cpp` with a comment on the behaviour under test and the failure it guards against; `tests/ShouldRecieveTCP/main.cpp` and `tests/ShouldSurviveConnectionChurn/main.cpp` show the shape, and a regression test names the bug.
|
|
- A `Check(ok, what)` helper in an anonymous namespace counts failures and prints them (`PASS`/`FAIL` lines in the newer tests), and `main` returns nonzero at the end; the oldest QUIC tests return at the first failure. Do not stop at the first failure unless later steps are meaningless without it.
|
|
- Each test owns fixed ports, distinct across the suite because the runner is parallel: 8090 to 8097 and 8110 to 8114 for HTTP/1.1 listeners, 8082 to 8086 and 8098 for HTTP/3, 9181, 9182, 9187 and 9188 for QUIC, 19200 to 19224 for the churn test; the two TCP tests bind ephemeral ports, and `ShouldBuildWithoutQUIC` and `ShouldSend` bind none. Pick an unused one for a new test, or bind an ephemeral port yourself (`LoopbackListener` in `ShouldConfigureTCP`).
|
|
- `ThreadPool::Start()` first, for anything that enqueues: every `*Async` call, `ClientHTTP` (its constructor installs an `OnStream` drain that runs as pool tasks), `ListenerHTTP`, `ListenerQUIC`, and the `*Async`-dispatch `Listen*` variants of `ListenerTCP`; `ListenerHTTP1` and the sync TCP listeners need none, which is why ten tests never call it. `Start()` runs `hardware_concurrency() - 1` workers and `Start(1)` none at all (tasks then wait for a `JoinPool()` nobody calls), so don't use `Start(1)` to make callbacks deterministic.
|
|
- A QUIC test ends in `std::_Exit(code)`, after `std::fflush(stdout)` since `_Exit` flushes nothing (`ShouldReportClosedQUICStream` does; the older ones skip the flush and can lose their last line in the log): msquic's two registrations are closed by static destructors that block until every connection is torn down, and the peers a listener accepted are heap-allocated for the callback's owner, so returning from `main` waits for the idle timeout. `ShouldSurviveConnectionChurn` is the exception on purpose: that wait is its assertion, so it deletes everything and returns, under a watchdog thread that calls `std::_Exit(1)` after 90 s, which the runner's 60 s timeout beats; a watchdog meant to fire goes under 60 s, as the 8 s one in `ShouldNotDropEarlyStreams` does. Any test that can hang gets one.
|
|
- Credentials: `QUICServerCredentials{ .selfSigned = true }` against `QUICClientCredentials{ .insecureNoServerValidation = true }`; for TLS, `TLSServerCredentials{ .selfSigned = true }` against a client given `GetSelfSignedCertificatePem().certificate` as `caPem`, so verification is exercised rather than skipped.
|
|
- A `ClientTCP` drives the wire where `ClientHTTP1` would hide the thing under test (pipelining in `ShouldSendRecieveHTTP1`, garbage and a stalled peer in `ShouldSurviveAbuseHTTP1`), and raw POSIX sockets (headers before the imports) where `ClientTCP` itself is under test (`ShouldRecieveTCP`, `ShouldConfigureTCP`, `ShouldBuildWithoutQUIC`).
|
|
- The outside world: `curl`, `python3 -m http.server` and the `openssl` CLI are probed with `command -v`, and a missing one skips that half with a line saying so. `ShouldSend` is the one test that needs the network.
|
|
|
|
A bug fix ships with a test that fails before it and passes after, in its own `test:` commit when it stands on its own; the `fix(quic)` commits in `git log` show the pairing.
|
|
|
|
## Code style
|
|
|
|
Nothing is enforced by tooling; match the file you are in.
|
|
|
|
- The first two lines of every source are exactly `//SPDX-License-Identifier: LGPL-3.0-only` and `//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®`, no space after `//`, then a blank line. The example is MIT in the same shape. (`Crafter.Network-ClientQUIC-Browser.cpp` has stray text after its copyright line; fix it when you touch the file.)
|
|
- Four-space indentation in new code. `Crafter.Network-ClientTCP.cppm`, `-ListenerTCP.cppm` and parts of `-ClientTCP.cpp` and `-ListenerTCP.cpp` are tab-indented from the first version; match the block you are in and do not reindent files, it buries the real diff. `Crafter.Network.cppm` and `Crafter.Network-ListenerTCP.cppm` are CRLF and every other source LF; an editor that normalises line endings rewrites the whole file, the same burying.
|
|
- `import std;` everywhere. C headers (`<sys/socket.h>`, `<msquic.h>`, `<openssl/*.h>`) go in the global module fragment after `module;`, and in an interface only under `#ifndef CRAFTER_NETWORK_BROWSER`, since the wasm sysroot has none of them. Tests `#include` POSIX headers before their imports the same way.
|
|
- `PascalCase` for types and functions, `camelCase` for variables and members, `k`-prefixed constants in the codecs (`HTTP3::kFrameHeaders`). Exceptions are `<Thing>Exception` or `<Codec>ProtocolError`. `std::println` for output in new code; some tests still use `std::cout`.
|
|
- Spellings that are API: `Recieve` in every receive method and test name, and the `ListenSyncSync` family. Consumers build against the default branch, so a rename breaks all of them at once: only on purpose, with the consumers updated in the same move, and never for a new name.
|
|
- Comments say why: the RFC section, the failure mode, the race, the rejected alternative. The codebase carries long comments on purpose (the header of `:HTTP1`, the stream registry in `-ClientQUIC.cpp`, `MakeBidiHandler` in `-ListenerHTTP.cpp`); do not strip them, and write them for new non-obvious code. Commit bodies follow the same rule.
|
|
- Errors are exceptions on the synchronous paths and never leave a pool task: an `*Async` call reports through its callback (`onError(std::exception_ptr)` on TCP, `bool sent` and the empty chunk on QUIC streams, an error string on the HTTP clients), because an exception escaping a `ThreadPool` task ends the program and the browser bridge has no exception channel from JS.
|
|
- Timeouts are per call on `ByteStream`, members on the HTTP/1.1 endpoints, `QUICSettings` on QUIC, and `std::chrono::milliseconds` everywhere. A new blocking path gets a bound.
|
|
|
|
## Design rules
|
|
|
|
Decided deliberately; change them only with the maintainer's say-so.
|
|
|
|
1. **One source, three builds.** Native, `--no-quic` and the browser are the same files, with `#ifndef CRAFTER_NETWORK_BROWSER` around whole partitions, `#ifndef CRAFTER_NETWORK_NO_QUIC` in the umbrella, and two `-Browser` implementation units. No per-platform directory and no host-side `#ifdef` beyond those two.
|
|
2. **The browser is a client, and async.** No listener, no raw TCP and no `*Sync` method there; the JS bridge drives `fetch()` and `WebTransport` and calls back through wasm exports. An API that cannot be made async does not join the shared surface.
|
|
3. **One request and response type, one route map, one dispatch order** across HTTP/1.1 and HTTP/3: exact path, query-stripped path, `fallback`, 404. A handler moves between the two by changing a class name, and `ShouldFallbackUnknownRoutes` replays one table over both to keep it so.
|
|
4. **`fallback` is a hook, not a pattern syntax.** Consumers bring their router; the library never grows a second route table that can disagree with it.
|
|
5. **Ambiguous HTTP/1.1 framing is rejected, not guessed** (RFC 9112 §11.2): conflicting lengths, a length with chunked, whitespace before a colon, CR or LF in a value to send. Compatibility never buys a parse that could smuggle.
|
|
6. **`https://` is a constructor argument, not a class.** The HTTP/1.1 code holds a `ByteStream&` and never learns whether it is a `PlainStream` or a `TLSStream`; `:TLS` is protocol-agnostic and keeps OpenSSL out of the interface.
|
|
7. **One OpenSSL in the process.** msquic builds against the system OpenSSL (3.5 or newer), not its bundled quictls, whose reading of the system `openssl.cnf` broke inside the Flatpak; `:TLS` links the same `libssl`. Where that OpenSSL is too old the answer is `--no-quic`, not a second TLS stack.
|
|
8. **msquic is pinned, Crafter.Thread is not.** Externals from outside Catcrafts get a `commit`; sibling Catcrafts repositories track their default branch on purpose.
|
|
9. **Receives return what arrived.** No padded buffers, no truncation, no exception for a signal, and a receive of zero bytes returns at once. The async variants deliver through callbacks and never throw out of the pool.
|
|
10. **The HTTP/1.1 listener runs a thread per connection**, not pool tasks: keep-alive connections are idle most of their life and would pin the pool.
|
|
11. **msquic handles are refcounted, and streams drain before their connection closes.** `QUICStream::Stop` initiates, the asynchronous `SHUTDOWN_COMPLETE` closes, `~ClientQUIC` waits (bounded) for the registry to empty. Do not short-circuit it: closing a connection with streams open is an msquic bugcheck, and a leaked handle hangs `RegistrationClose` at exit.
|
|
12. **This project's flags stay here.** `--no-quic` is stripped before the args go to Crafter.Thread, so every project in a consumer's graph loads Crafter.Thread with the same args.
|
|
|
|
## Commit and PR guidelines
|
|
|
|
- Branch off `master` (`claude/issue-<n>` for issue work, `claude/<topic>` or `fix/<topic>` otherwise) and open the PR against `master` on the project's Forgejo at `forgejo.catcrafts.net/Catcrafts/Crafter.Network`. `origin` is the HTTPS URL for fetch and push on this clone; Crafter.Build's notes say the server refuses HTTPS pushes, so set an SSH push URL first (`git remote set-url --push origin forgejo@forgejo.catcrafts.net:Catcrafts/Crafter.Network.git`), as the Crafter.Build, Math, Asset and Graphics clones have.
|
|
- Subject: `type(scope): imperative summary`, with `feat`, `fix`, `docs` or `test` and the scopes in use: `tcp`, `tls`, `https`, `http1`, `http`, `quic`, `listener`. Body: what was wrong, why, and why the fix has this shape, wrapped at about 75 columns, with the numbers when a race was measured (`git log` for `fix(quic)` shows the voice), and a `Breaking:` paragraph when a signature changes. Add a `Co-Authored-By:` trailer when an agent wrote it.
|
|
- Separate commits for the fix, its test and the README change when each stands on its own. Update the README in the same PR when user-visible behaviour changes, and `AGENTS.consumer.md` when a signature, a default or a rule consumers lean on changes; consumers pick the notes up on their next build.
|
|
- `crafter-build test` and `crafter-build test --no-quic` green before pushing, and the wasm build when the browser surface was touched. There is no CI.
|
|
- Never commit `bin/`, `build/`, `AGENTS.md` or the example's build outputs. `examples/SimpleClient/cert-hash.txt` is tracked because `cfg.files` needs it to exist; the native server overwrites it at runtime, and a changed hash in a diff is noise.
|
|
|
|
## Gotchas
|
|
|
|
- **`ThreadPool::Enqueue` before `ThreadPool::Start()` is a null dereference**, and most of the library enqueues: every `*Async`, every QUIC `OnStream` and `OnDatagram` delivery, `ClientHTTP` and `ListenerHTTP` entirely. The crash has no message; in a test it shows as 💥.
|
|
- **Two self-signed certificates.** `QUICServerCredentials::selfSigned` is made by the `openssl` CLI and cached at `/tmp/crafter-network-quic-cert/`, reused across restarts while valid so a browser's pinned hash survives a restart, except inside the Flatpak, whose `/tmp` goes away with its last instance (see "Dev environment"); `TLSServerCredentials::selfSigned` is made in-process by `:TLS` and is new every start. `GetSelfSignedCertificatePath()` is the first, `GetSelfSignedCertificatePem()` the second.
|
|
- **`ComputeCertificateHashSHA256` and the QUIC certificate shell out to `openssl`** (`std::system`, `popen`) and throw `QUICException` without the CLI. `:TLS` could do both in-process; nobody has moved them yet.
|
|
- **msquic's registration is closed by a static destructor that waits for every handle.** A test that returns from `main` with an accepted peer alive waits two minutes, the idle timeout; the tests `_Exit`. The exception, `ShouldSurviveConnectionChurn`, exists because a leaked stream handle once hung that close forever (`fix(quic)` in `git log`, with the `SHUTDOWN_ON_FAIL` story).
|
|
- **Callbacks the interfaces say run "on the msquic worker" run as pool tasks**: `ClientQUIC::OnStream` and `OnDatagram` both enqueue. What arrives before a handler is set is queued: streams are delivered on `OnStream`; datagrams go to `RecieveDatagramSync`'s queue and never to a later `OnDatagram`.
|
|
- **`ListenerQUIC::ListenAsync*` run the accept loop on a `std::thread`**, not the pool as their comments say, and `ListenerHTTP::Listen` is `ListenSyncAsync` underneath, so a `ListenerHTTP`'s per-connection setup is a pool task. A saturated pool delays a new connection's SETTINGS; the early streams are buffered meanwhile (`ShouldNotDropEarlyStreams`).
|
|
- **A `ClientQUIC` against an HTTP/3 server must drain the server's unidirectional streams** (control, QPACK encoder and decoder) with an `OnStream` reader, or they back up and msquic may abort the connection. `ClientHTTP` does it; a hand-rolled WebTransport client (`ShouldEchoWebTransport`) does it itself.
|
|
- **`QUICSettings::peerUnidiStreamCount` is the first thing to raise** when a peer stalls opening streams; it was a hardcoded 16 until `ShouldHonourQUICSettings`.
|
|
- **The comments about `-fno-exceptions` in `project.cpp`, the umbrella and the browser unit are history.** crafter-build 4.0.0 compiles wasm with exceptions on and refuses `-fno-exceptions`; `:HTTP3` stays out of the browser build and the browser units still report through callbacks because the JS side has no exception channel, not because `throw` would not compile.
|
|
- **The header comment of `Crafter.Network-HTTP3.cppm` is stale**: the partition is exported from the umbrella natively and has a Huffman decoder. The README is right.
|
|
- **More comments the code outgrew**: `-ListenerQUIC.cpp` says the certificate pair lives in a `mkdtemp`'d directory for the life of the process (it is the fixed `/tmp/crafter-network-quic-cert/`, reused) and is valid 13 days (`-days 10`); `ClientQUIC.cppm` calls `selfSigned` an in-memory certificate (it is on disk, made by the CLI); `ListenerHTTP.cppm` says `Listen()` dispatches on the calling thread (it is `ListenSyncAsync`); the link-flag comment in `project.cpp` still has msquic bundling quictls (it links the system `libssl`). Fix a comment when you touch its code.
|
|
- **`WebTransportSession::OpenStream(true)` does not throw** as its comment says; it writes the unidirectional prefix. Peer-initiated unidirectional WebTransport streams are drained by the listener and never reach a session.
|
|
- **`ListenerTCP::concurrentClientLimit` is stored and never read**; only `totalClientLimit` ends the loop.
|
|
- **`ListenerTCP(port, ...)` listens on every IPv4 address**; given an address it binds that one, IPv4 or IPv6, and port 0 asks the kernel for one. `ClientTCP` resolves for both families and tries every address; QUIC listens and connects on either family (`QUIC_ADDRESS_FAMILY_UNSPEC`).
|
|
- **The Flatpak's host triple is `x86_64-unknown-linux-gnu`**, and the tests register on any Linux triple since the TCP-only commit; before it they never ran inside the Flatpak.
|
|
- **A port in use from a crashed run** is `Could not bind port <n>: Address already in use` on the HTTP/1.1 side and a `ListenerStart failed` `QUICException` on the QUIC side; find the stray process rather than changing the port.
|
|
- **A warning is printed only when its unit actually recompiles**; a build with nothing to do is silent and says nothing about warnings.
|