Crafter.Network/interfaces/Crafter.Network-ClientTCP.cppm

125 lines
5.5 KiB
Text
Raw Normal View History

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 19:07:29 +02:00
//SPDX-License-Identifier: LGPL-3.0-only
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
module;
#ifndef CRAFTER_NETWORK_BROWSER
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <strings.h>
#include <cerrno>
#endif
export module Crafter.Network:ClientTCP;
import std;
#ifndef CRAFTER_NETWORK_BROWSER
namespace Crafter {
export class SocketClosedException : public std::exception {
public:
const char* what() const noexcept override {
return "Socket closed";
}
};
// A connect or a receive waited longer than its timeout.
export class SocketTimeoutException : public std::exception {
public:
const char* what() const noexcept override {
return "Socket timed out";
}
};
// Where the code of a SocketErrorException comes from.
export enum class SocketErrorKind {
// An errno: ECONNREFUSED, ENETUNREACH, EMFILE, ...
System,
// A getaddrinfo result: EAI_NONAME, EAI_AGAIN, ...
Resolver,
};
// A resolve, a socket(), a connect, a send or a receive that failed, with
// the code it failed with. what() is the text ClientTCP has always thrown, and catching
// std::runtime_error still catches it; the code is for callers that
// report the cause in their own words — a Python-style
// "[Errno 111] Connection refused", say — and would otherwise have to
// parse it back out of the message.
export class SocketErrorException : public std::runtime_error {
public:
SocketErrorException(SocketErrorKind kind, std::int32_t code, const std::string& message)
: std::runtime_error(message), kind(kind), code(code) {}
SocketErrorKind kind;
std::int32_t code;
};
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 19:07:29 +02:00
export class ClientTCP {
public:
int socketid;
ClientTCP(int socket);
// The host is resolved for IPv4 and IPv6 alike and every address it
// resolves to is tried in turn, the way Python's
// socket.create_connection does: `localhost` reaches a server that
// listens on ::1 only, as a dev server on a recent Node does. The
// first address that connects wins; when none does, what the last
// one failed with is thrown — SocketErrorException, or
// SocketTimeoutException for a timeout.
//
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 19:07:29 +02:00
// A non-zero connectTimeout makes a connect nobody answers throw
// SocketTimeoutException after that long, where it would otherwise
// wait out the kernel's retries, about two minutes. It bounds each
// address on its own. A refused connect fails at once either way.
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 19:07:29 +02:00
ClientTCP(const char* host, std::uint16_t port, std::chrono::milliseconds connectTimeout = std::chrono::milliseconds::zero());
ClientTCP(std::string host, std::uint16_t port, std::chrono::milliseconds connectTimeout = std::chrono::milliseconds::zero());
~ClientTCP();
ClientTCP(const ClientTCP&) = delete;
ClientTCP(ClientTCP&& other) noexcept;
void Connect(std::chrono::milliseconds timeout = std::chrono::milliseconds::zero());
void Stop();
// Turns Nagle's algorithm off (TCP_NODELAY), so a small write goes
// out at once instead of waiting for the ACK of the one before it.
// For small messages that must arrive promptly, such as a stream of
// frames.
void SetNoDelay(bool enabled);
// A receive that waits longer than this throws
// SocketTimeoutException, losing what a whole-buffer receive had read
// so far. Zero, the default, waits for good.
void SetReceiveTimeout(std::chrono::milliseconds timeout);
void Send(const void* buffer, std::uint32_t size) const;
// The receives return what arrived, at least one byte; asked for none,
// none, at once, without looking at the socket. They throw
// SocketClosedException once the peer has closed, and
// SocketTimeoutException when a receive timeout runs out.
std::vector<char> RecieveSync() const;
std::vector<char> RecieveUntilCloseSync() const;
std::vector<char> RecieveUntilFullSync(std::uint32_t bufferSize) const;
std::vector<char> RecieveSync(std::uint32_t bufferSize) const;
// Into `buffer`; returns how many bytes it put there.
std::uint32_t RecieveSync(std::uint32_t bufferSize, void* buffer) const;
// The same receives on Crafter.Thread's ThreadPool. What arrived goes
// to the callback; what a receive throws goes to onError instead (an
// empty one drops it), as an exception left to escape a pool task
// would end the program.
void RecieveAsync(std::function<void(std::vector<char>)> recieveCallback, std::function<void(std::exception_ptr)> onError) const;
void RecieveUntilCloseAsync(std::function<void(std::vector<char>)> recieveCallback, std::function<void(std::exception_ptr)> onError) const;
void RecieveAsync(std::uint32_t bufferSize, std::function<void(std::vector<char>)> recieveCallback, std::function<void(std::exception_ptr)> onError) const;
void RecieveUntilFullAsync(std::uint32_t bufferSize, std::function<void(std::vector<char>)> recieveCallback, std::function<void(std::exception_ptr)> onError) const;
// Into `buffer`, which must outlive the receive; the callback gets the count.
void RecieveAsync(std::uint32_t bufferSize, std::function<void(std::uint32_t)> recieveCallback, char* buffer, std::function<void(std::exception_ptr)> onError) const;
private:
struct Address {
sockaddr_storage storage;
socklen_t length;
};
// What the host resolved to, in getaddrinfo's order; empty for a
// socket handed over by accept().
std::vector<Address> addresses;
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 19:07:29 +02:00
};
}
2026-05-19 02:53:50 +02:00
#endif