Commit graph Crafter.Network/README.md
Author SHA1 Message Date
f29c4a83d2
docs(quic): OnRecieve and Abort, and serving a stream without a thread
The README's QUIC section gains receiving by callback: a server
answering one request per stream with a size cap enforced by Abort,
the threads the callbacks run on, what they may call, and the
std::stop_callback rule for destroying the wrapper; its follower
example now drops a lagging peer with Abort rather than a graceful
Stop. The consumer notes give the full contract, the weak_ptr idiom for
reaching a stream from its own callbacks, and list the callbacks among
what runs on msquic's workers; the agent notes count the new test,
describe the per-stream delivery in the partition list, and add the
callbacks to the worker-thread gotcha.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 02:33:28 +01:00
328561fa91
docs(quic): SendAsync holds no thread; BytesInFlight and SendsInFlight
The README's QUIC section shows sending without a thread: what onSent's
true and false mean, the thread it runs on and what that forbids, and
BytesInFlight as the way to drop a follower that cannot keep up. The
consumer notes give the full contract, take SendAsync out of the list
of what goes through the pool (but for a send msquic never took) and
into what runs on msquic's workers; the agent notes count the new test
and add onSent to the callbacks that really do run on the worker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 02:24:39 +01:00
3be8642d32
docs(quic): OnClosed, and what ~ClientQUIC and SendSync now promise
The README's QUIC section gains how to own accepted connections: the
OnClosed callback, the thread it runs on, what not to do in it and the
std::stop_callback rule that makes deleting a connection under a lock
its callback takes a deadlock. The consumer notes give the same
contract with the map-under-a-mutex pattern for a server that also
deletes on shutdown, list OnClosed among what runs on msquic's workers
rather than the pool, say that ~ClientQUIC now waits up to 1 s so the
peer hears the close, and that SendSync throws only for its own send.
The agent notes count the three new tests, add OnClosed's thread and
the destructor's new wait to the gotchas, and the forking tests and
their alarm() watchdog to the testing conventions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 02:20:04 +01:00
76e927595d
docs(listener): where the selfSigned QUIC certificate lives now, and that it needs no CLI
The README's self-signed section, its dependency list, the consumer
notes and the agent notes described the openssl CLI and the shared
/tmp/crafter-network-quic-cert/. They now give the per-user location
and its fallbacks, the day-before-expiry rollover (which is when a
pinned hash changes), what keeps processes that start together on one
pair, and that nothing has to be installed. The agent notes' dev
environment says where the Flatpak keeps the certificate between runs,
drops the two gotchas the move resolved (the CLI, and the comments in
-ListenerQUIC.cpp and ClientQUIC.cppm that disagreed with the code),
and notes the module-internal functions :TLS declares for the listener.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:57:57 +01:00
5068f0acd7
docs(listener): ListenerQUIC's bind address, and how msquic enforces it
The README's QUIC section and the consumer notes show the new
constructor and say what binding an address means under msquic: the UDP
port is bound on every address anyway, and a connection sent to another
one is refused during its first flight (0x5c, in milliseconds), so a
client of a 127.0.0.1 listener dials 127.0.0.1, not `localhost`. The
agent notes' family gotcha says the same and counts the new test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 01:49:01 +01:00
fdb0ac2524
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>
2026-11-01 01:46:29 +01:00
7ee938c98e
feat(http1): ClientHTTP1::Fetch follows redirects by the RFC, on a URL type
One request to an http(s) URL with redirects followed by RFC 9110 §15.4:
301, 302, 303, 307 and 308 for every method; a 303 makes the request a
GET without its body and content headers, a 301 or 302 does so for a POST
alone, a 307 or 308 keeps the method and the body. The Location is
resolved against the sending URL by RFC 3986, a hop between http and
https is allowed, Authorization, Proxy-Authorization and Cookie are
dropped on a change of origin, and each hop is a client of its own, since
one ClientHTTP1 is one origin. A redirect without a usable Location, to
another scheme, back to a URL already visited or past maxRedirects comes
back as the response, with `stop` saying why.

The new :URL partition is the URL type behind it, exported: Parse,
Resolve (RFC 3986 §5.2; its §5.4 examples are the test), ToString, and
the pieces a request is built from.

This replaces the FetchHTTP1 free function, which followed redirects the
way Python's urllib did (its repeat limit, its quoting of the Location, a
POST turned back on a 307) for a port that no longer needs the fidelity.

Breaking: FetchHTTP1, FetchOptionsHTTP1 and FetchResultHTTP1 are gone;
use ClientHTTP1::Fetch, FetchOptions and FetchResult, whose `url` is a
URL and whose `limited` became `stop`.
2026-10-31 20:39:43 +01:00
e86c3b70a0
feat(tcp): socket errors as std::system_error, with the phase that failed
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().
2026-10-31 20:33:37 +01:00
16da0a1979
feat(tcp): a failed send or receive says which errno it failed with
Send() and the receives threw a bare std::runtime_error with the
strerror text, so a caller that reports the cause the way Python does -
"the connection broke ([Errno 104] Connection reset by peer)", which a
language model reads in Crafter.Review's SQL helper output - could not
tell the code. They throw SocketErrorException now, as a failed connect
already does: kind System, the errno, and the same message, still a
std::runtime_error. A receive timeout stays SocketTimeoutException and
a closed peer SocketClosedException.

ShouldReportIoErrors: a peer that resets the connection (SO_LINGER 0)
fails the next receive with ECONNRESET and a send into it with EPIPE
or ECONNRESET.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:28:32 +01:00
f9a45c143e
feat(http1): bound the connect, and FetchHTTP1 for a URL with redirects
- ClientHTTP1 gains connectTimeout (default 15 s), applied to each
  address the host resolves to. `timeout` only ever bounded the waits for
  a response, so a host that drops the SYNs held Send() for the kernel's
  retries, about two minutes, whatever the caller had asked for.
- The default Host header puts an IPv6 literal in brackets.
- FetchHTTP1(url, options) makes one request to an http:// or https://
  URL and follows redirects as Python's urllib does, so urllib code ported
  over keeps its behaviour: 301/302/303/307/308 for GET and HEAD,
  301/302/303 for POST (re-sent as a GET without its body), a relative
  Location resolved as urljoin resolves it and percent-encoded as urllib
  quotes it, http and https hops mixed, each redirect's body read before
  the next request, and the chain stopped by urllib's limits (4 repeats
  of one URL, 10 different URLs) with the last redirect returned and
  `limited` set. Proxies are not consulted.

New: ShouldTimeoutSilentHTTP1, ShouldFollowRedirectsHTTP1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:28:32 +01:00
d6f8f4edcb
feat(tcp): ListenerTCP binds the address it is given, at port 0 if asked
A new constructor takes a numeric bind address (127.0.0.1, ::1, 0.0.0.0,
::) and accepts port 0, and Port() reports the port the listener got.
Until now every listener bound 0.0.0.0 at a port the caller chose, so a
helper meant for this machine's own processes was reachable from the
network, and the caller had to guess a free port. A name such as
`localhost` is refused instead of bound to whichever address the
resolver ranks first. The port-only constructor still listens on every
IPv4 interface.

`running` is atomic now: Stop() runs on another thread than the accept
loop that reads it. The accept loops take a sockaddr_storage, so an IPv6
peer's address is no longer cut short.

New: ShouldBindLoopbackEphemeral.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:28:32 +01:00
78853033e8
feat(tcp): connect over IPv4 and IPv6, and say why a connect failed
- ClientTCP resolves with AF_UNSPEC and tries every address the name
  resolves to, in getaddrinfo's order, the way Python's
  socket.create_connection does. It used to ask for AF_INET only and keep
  the first answer, so `localhost` could not reach a server listening on
  ::1 alone, which is what a dev server on a recent Node does. The connect
  timeout bounds each address on its own; when none connects, what the
  last one failed with is thrown.
- A failed resolve, socket() or connect throws SocketErrorException: the
  std::runtime_error it always was, with the same message, plus the errno
  (SocketErrorKind::System) or the getaddrinfo code
  (SocketErrorKind::Resolver). A caller that reports the cause in its own
  words, such as a Python-style "[Errno 111] Connection refused", no
  longer has to parse it back out of the text.
- The move constructor carries the resolved addresses along.

New: ShouldConnectDualStack, ShouldReportConnectErrors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:28:32 +01:00
f683216850
feat: TCP-only builds; TCP options and timeouts; receives that report and keep what arrived
- --no-quic leaves QUIC, HTTP/3 and WebTransport out of the build, and with
  them msquic: a consumer that only needs TCP builds no msquic. The flag
  isn't passed on to dependencies.
- --local takes Crafter.Thread from the sibling checkout, as the other
  Crafter projects do.
- ClientTCP gains SetNoDelay, SetReceiveTimeout, a connect timeout and
  SocketTimeoutException.
- TCP receives return the bytes that arrived:
  - RecieveSync(n) returned the whole buffer when fewer had come;
  - RecieveUntilCloseSync truncated what it had read;
  - a signal (EINTR) during a receive threw where it should wait on;
  - RecieveSync(size, buffer) returns the count and throws like the others;
  - a receive of no bytes returns at once: recv's 0 for it isn't a close.
- Every TCP *Async receive takes an onError(std::exception_ptr): an
  exception escaping a ThreadPool task ended the program. An empty onError
  drops the error.
- QUIC streams:
  - async receives deliver an empty chunk on close, as the browser build does;
  - RecieveUntilFullAsync gives what arrived on a stream that closes early;
  - RecieveUntilFullSync takes all it asked for or nothing, under one hold
    of the lock, so what did arrive stays for the next receive and no
    other receive can take bytes from among it;
  - SendAsync's callback hears whether the send went out (bool sent): native,
    browser, the JS bridge and the example.
- msquic is pinned to v2.6.3 and built against the system OpenSSL
  (QUIC_TLS_LIB=openssl, 3.5 or newer). Its bundled quictls read the system's
  openssl.cnf, and the Flatpak runtime's (a PKCS#11 engine, with
  config_diagnostics on) made it fail to initialise. The README says to use
  --no-quic where the distro's OpenSSL is older.
- Tests now register on any Linux triple: they never ran under the Flatpak's
  x86_64-unknown-linux-gnu. New: ShouldBuildWithoutQUIC, ShouldConfigureTCP,
  ShouldRecieveTCP, ShouldReportClosedQUICStream.

Breaking: SendAsync's callback takes a bool, the TCP *Async receives take
an onError, and RecieveSync(size, buffer) returns the count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:04:57 +02:00
catbot
25e8c84794 test(quic): cover connection teardown
Every other QUIC test ends in std::_Exit(0), so nothing in the suite ever
ran a ~ClientQUIC. This adds 25 build-up/tear-down cycles with reader
threads parked inside RecieveSync -- the shape a reconnect loop has --
which aborts on ~10/20 runs against the current implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 17:06:25 +00:00
catbot
331cf0c66c docs: describe HTTPS, the TLS layer, and its system dependency
The HTTP/1.1 section promised the opposite of what the code now does — a
"No TLS" heading stating there was no plan to link a TLS stack into this
path. Replace it with what to actually pass, and lead with the part that
gets deployed wrong: verifying the chain without the hostname is not a
check, and a private trust anchor is the answer for a self-signed peer
rather than insecureNoServerValidation.

Also document :Stream and :TLS as modules in their own right — TLSStream
is a ByteStream over any descriptor, not something only HTTP can use —
and record libssl as a system dependency, including why it is not
vendored the way msquic is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 20:23:40 +00:00
catbot
bb25250e75 docs: document fallback routing for both listeners
New "Routes that cannot be enumerated" section, linked from both
listener sections since the hook and its precedence are identical on
each. Also records the HTTP/3 query-strip change and why a fallback has
to be a constructor argument on the ListenerAsync* wrappers.
2026-07-28 19:42:16 +00:00
catbot
a79ab6a024 docs(http1): document the HTTP/1.1 stack, and expose the client's timeout
README: HTTP/1.1 in the intro, feature list, module list, browser-build
exclusions, dependencies and test list, plus a Components section
covering both classes, the standalone codec, what is and is not
implemented, the smuggling-shaped inputs that are rejected, and an
explicit note that this path is plaintext and belongs behind a TLS
terminator.

ClientHTTP1::timeout was hard-coded and invisible; make it a public
member alongside `limits`, mirroring the listener's timeouts.

Also pipelining coverage in ShouldSendRecieveHTTP1: two requests written
before either is answered, driven from a raw socket since ClientHTTP1
waits for each response.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 01:02:37 +00:00
e33ec5b72e SPDX license 2026-07-22 17:55:45 +02:00
e8630528af browser wasm 2026-05-19 02:53:50 +02:00
28fab2509b full QUIC support 2026-05-07 00:06:44 +02:00
45479a46ff added QUIC 2026-05-06 04:06:17 +02:00
de2073422c crafter build V2 2026-05-06 01:09:40 +02:00
5bfee7f955 added readme 2025-11-03 17:52:45 +01:00
0fbc5bad52 initial commit 2025-11-02 15:00:53 +01:00