docs(listener): ListenerQUIC::Port(), and port 0 for tests
The README gains a QUIC section, which it never had: plain QUIC under an ALPN of your own, starting with a listener on port 0 and Port(). The consumer notes describe Port() and point tests at port 0 rather than at binding a socket by hand; the agent notes count the new test and make port 0 the default for a new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3457ff08ce
commit
fdb0ac2524
3 changed files with 22 additions and 4 deletions
|
|
@ -82,7 +82,7 @@ A route map is `std::unordered_map<std::string, std::function<HTTPResponse(const
|
|||
- **`QUICStream`** is move-only, with `connection`, `canSend`, `canReceive` (a unidirectional stream has one of them false) and the msquic `handle`. `SendSync(buffer, size, finish = false)` blocks until msquic has taken the buffer, throws `QUICClosedException` on a closed stream or connection and `QUICException` on a failed send; `finish` closes the send side after it (the peer's `RecieveUntilCloseSync` returns). `RecieveSync()` waits for at least one byte and throws `QUICClosedException` once the peer closed its side and the buffer is drained; `RecieveUntilCloseSync()`; `RecieveUntilFullSync(n)` takes exactly `n` or throws and leaves what arrived for the next receive. `SendAsync(buffer, size, finish, onSent(bool))` copies the buffer and tells the callback whether the stream took it; `RecieveAsync`, `RecieveUntilCloseAsync` and `RecieveUntilFullAsync(n, ...)` give a closed stream an empty chunk and an early close what arrived, never an exception. A default-constructed `QUICStream` behaves as closed. `PrependReceived(bytes)` pushes bytes back for the next receive (a demuxer that peeked a prefix); `GetStreamId()` throws `QUICException` before the stream has started; `Stop()` shuts both directions down (the send side with a FIN, the receive side abortively, else a peer holding its side open keeps the handle alive), completes asynchronously, and is what the destructor calls.
|
||||
- Lifetimes: `~ClientQUIC` first destroys peer streams nobody claimed, then shuts the connection down and closes it, waiting up to 5 s for every stream on it to finish closing. Streams may outlive their connection, dead; destroy or `Stop()` them first when you can. msquic has two process-wide registrations here, one for clients and one for listeners and the peers they accept, each closed by a static destructor at exit, and each close blocks until every connection and stream handle opened under it is closed: an accepted peer you never deleted holds the listener's until the idle timeout, two minutes by default. Delete or `Stop()` what you own before `main` returns, or end with `std::_Exit` after flushing, as the library's own tests do.
|
||||
|
||||
**`ListenerQUIC(port, alpn, QUICServerCredentials, connectCallback(ClientQUIC*), concurrentClientLimit, totalClientLimit)`** listens on every address of both families and throws `QUICException` when it cannot. The callback's `ClientQUIC*` is heap-allocated and yours: set `OnStream` / `OnDatagram` on it, keep it as long as the connection should live, `Stop()` or `delete` it to shut the connection down, and `delete` it in any case, since only that frees it (a connection the peer or the idle timeout ended has its msquic handle closed for it, but the object stays). `ListenSyncSync()` runs the accept loop on the calling thread and the callback there; `ListenSyncAsync()` runs the callback as a pool task; `ListenAsyncSync()` and `ListenAsyncAsync()` run the loop on a thread of their own, the second with the callback on the pool. `Stop()` stops accepting; the destructor stops, joins, and deletes peers accepted but not yet handed out. `totalClientLimit` ends the hand-off loop after that many connections (later ones queue until the listener is destroyed); `concurrentClientLimit` is stored and not enforced.
|
||||
**`ListenerQUIC(port, alpn, QUICServerCredentials, connectCallback(ClientQUIC*), concurrentClientLimit, totalClientLimit)`** listens on every address of both families and throws `QUICException` when it cannot. `Port()` is the UDP port it bound: pass port 0 to let msquic pick a free one and read it back (still valid after `Stop()`, 0 on a moved-from listener). The callback's `ClientQUIC*` is heap-allocated and yours: set `OnStream` / `OnDatagram` on it, keep it as long as the connection should live, `Stop()` or `delete` it to shut the connection down, and `delete` it in any case, since only that frees it (a connection the peer or the idle timeout ended has its msquic handle closed for it, but the object stays). `ListenSyncSync()` runs the accept loop on the calling thread and the callback there; `ListenSyncAsync()` runs the callback as a pool task; `ListenAsyncSync()` and `ListenAsyncAsync()` run the loop on a thread of their own, the second with the callback on the pool. `Stop()` stops accepting; the destructor stops, joins, and deletes peers accepted but not yet handed out. `totalClientLimit` ends the hand-off loop after that many connections (later ones queue until the listener is destroyed); `concurrentClientLimit` is stored and not enforced.
|
||||
|
||||
- `selfSigned` shells out to the `openssl` CLI (install it, or pass `certPath`/`keyPath`; without it the constructor throws `QUICException`) and caches the pair at `/tmp/crafter-network-quic-cert/{cert,key}.pem`, reused across restarts while valid (inside crafter-build's Flatpak, `/tmp` is a per-app directory discarded once no instance is running, so there the pair rarely survives between runs; pass `certPath`/`keyPath` for a stable certificate): ECDSA P-256, `CN=localhost`, SANs `localhost`, `127.0.0.1`, `::1`, 10 days, with the extensions Chromium's `serverCertificateHashes` demands. `GetSelfSignedCertificatePath()` is that file, and `ComputeCertificateHashSHA256(certPath)` (also through `openssl`) is the 32-byte digest a browser pins, see "Browser build". A different certificate from the TLS partition's: a client of both needs both.
|
||||
|
||||
|
|
@ -135,6 +135,6 @@ On a `wasm32-*` target the same `ClientHTTP` and `ClientQUIC` compile against th
|
|||
### Testing a consumer
|
||||
|
||||
- Start the pool first, and end a QUIC test with `std::fflush(stdout); std::_Exit(code);`, or delete every accepted peer and `Stop()` every client before `main` returns; otherwise the exit waits for the idle timeout.
|
||||
- crafter-build runs tests in parallel, so each test binds a port of its own. `ListenerTCP` and `ListenerHTTP1` set `SO_REUSEADDR`, so a port in `TIME_WAIT` from the previous run is fine; one held by a stray server from a crashed run is `Could not bind port`. For an ephemeral port, bind a socket yourself and read it back with `getsockname`, as `tests/ShouldConfigureTCP` does, then hand it to a `ClientTCP`.
|
||||
- crafter-build runs tests in parallel, so each test binds a port of its own. `ListenerTCP` and `ListenerHTTP1` set `SO_REUSEADDR`, so a port in `TIME_WAIT` from the previous run is fine; one held by a stray server from a crashed run is `Could not bind port`. Simpler is no fixed port at all: `ListenerTCP(address, 0, ...)` and `ListenerQUIC(0, ...)` take whatever port is free and report it with `Port()`.
|
||||
- `QUICServerCredentials{ .selfSigned = true }` against `QUICClientCredentials{ .insecureNoServerValidation = true }` is the local pair; `TLSServerCredentials{ .selfSigned = true }` against `TLSClientCredentials{ .caPem = GetSelfSignedCertificatePem().certificate }` exercises real verification. Neither needs anything installed except, for QUIC, the `openssl` CLI (the Flatpak has it).
|
||||
- Give a blocked reader (`RecieveSync` in a loop) a `std::thread`, not a pool task, and a test that could hang a watchdog thread that calls `std::_Exit(1)` sooner than the runner's 60 s timeout, as `tests/ShouldNotDropEarlyStreams` does at 8 s (the churn test's 90 s watchdog never fires first).
|
||||
|
|
|
|||
Loading…
Reference in a new issue