Crafter.Network/interfaces/Crafter.Network-ClientTCP.cppm
Jorijn van der Graaf 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

89 lines
No EOL
3.9 KiB
C++
Executable file

//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";
}
};
export class ClientTCP {
public:
int socketid;
ClientTCP(int socket);
// 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. A refused connect
// fails at once either way.
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:
sockaddr_in serv_addr;
};
}
#endif