Crafter.Network/interfaces/Crafter.Network-ClientQUIC.cppm

251 lines
11 KiB
Text
Raw Normal View History

2026-05-06 04:06:17 +02:00
/*
Crafter®.Network
Copyright (C) 2026 Catcrafts®
Catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3.0 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
2026-05-19 02:53:50 +02:00
#ifndef CRAFTER_NETWORK_BROWSER
2026-05-06 04:06:17 +02:00
#include <msquic.h>
2026-05-19 02:53:50 +02:00
#endif
2026-05-06 04:06:17 +02:00
export module Crafter.Network:ClientQUIC;
import std;
namespace Crafter {
export class ListenerQUIC;
export class QUICException : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
export class QUICClosedException : public std::exception {
public:
const char* what() const noexcept override { return "QUIC connection closed"; }
};
// Server certificate sources. Pick one of (filePaths) or (selfSigned).
// selfSigned generates an in-memory ephemeral cert — fine for dev/LAN.
export struct QUICServerCredentials {
std::string certPath;
std::string keyPath;
bool selfSigned = false;
};
// Client-side credential validation. By default we require a real cert.
2026-05-19 02:53:50 +02:00
// insecureNoServerValidation disables peer cert checks — only for dev,
// and silently ignored in the browser build (browsers enforce their own
// certificate policy). For browser dev against a self-signed listener,
// populate serverCertificateHash with the SHA-256 of the server's DER
// certificate; on the browser it is forwarded to WebTransport's
// serverCertificateHashes option. A zeroed array means "unused" — the
// browser will then require a publicly trusted cert.
2026-05-06 04:06:17 +02:00
export struct QUICClientCredentials {
bool insecureNoServerValidation = false;
2026-05-19 02:53:50 +02:00
std::array<std::uint8_t, 32> serverCertificateHash{};
2026-05-06 04:06:17 +02:00
};
export class ClientQUIC;
2026-05-07 00:06:44 +02:00
// A reliable, ordered stream within a QUIC connection. May be
// bidirectional or unidirectional; for unidi streams either canSend or
// canReceive will be false depending on which side initiated. Owned by
// ClientQUIC; do not destroy directly. Obtain via ClientQUIC::OpenStream
// (optionally with unidirectional=true) or via the on-stream callback
// for inbound streams initiated by the peer.
2026-05-06 04:06:17 +02:00
export class QUICStream {
public:
2026-05-19 02:53:50 +02:00
#ifndef CRAFTER_NETWORK_BROWSER
2026-05-06 04:06:17 +02:00
// Underlying msquic HQUIC handle. Treated as opaque by callers.
HQUIC handle = nullptr;
2026-05-19 02:53:50 +02:00
#endif
2026-05-06 04:06:17 +02:00
// The connection that owns this stream (non-owning).
ClientQUIC* connection = nullptr;
2026-05-07 00:06:44 +02:00
// Direction flags. Bidi streams have both true; outgoing unidi sets
// canReceive=false; incoming unidi (peer-initiated) sets canSend=false.
bool canSend = true;
bool canReceive = true;
QUICStream();
2026-05-19 02:53:50 +02:00
#ifndef CRAFTER_NETWORK_BROWSER
2026-05-06 04:06:17 +02:00
QUICStream(HQUIC handle, ClientQUIC* connection);
2026-05-19 02:53:50 +02:00
#else
// Browser-only constructor: wraps a JS-side WebTransport stream
// identified by its integer handle. Used by ClientQUIC::OpenStream
// and by the incoming-stream dispatcher in the JS bridge — not
// intended for direct use.
QUICStream(std::int32_t handle, ClientQUIC* connection,
bool canSend, bool canReceive);
#endif
2026-05-06 04:06:17 +02:00
~QUICStream();
QUICStream(const QUICStream&) = delete;
QUICStream(QUICStream&&) noexcept;
QUICStream& operator=(QUICStream&&) noexcept;
2026-05-19 02:53:50 +02:00
#ifndef CRAFTER_NETWORK_BROWSER
2026-05-06 04:06:17 +02:00
// Send a buffer. If finish=true, the send-side of the stream is closed
// after the buffer is delivered (peer will see graceful shutdown).
// Blocks until msquic accepts the buffer; throws on stream/conn close.
void SendSync(const void* buffer, std::uint32_t size, bool finish = false);
// Block until at least one byte is received; returns the received
// chunk. Throws QUICClosedException once the peer has closed the
// send-side and the buffer is drained.
std::vector<char> RecieveSync();
// Read until the peer closes the send-side, accumulating all chunks.
std::vector<char> RecieveUntilCloseSync();
// Read exactly bufferSize bytes; throws if the peer closes early.
std::vector<char> RecieveUntilFullSync(std::uint32_t bufferSize);
2026-05-19 02:53:50 +02:00
#endif
2026-05-06 04:06:17 +02:00
2026-05-19 02:53:50 +02:00
// Send a buffer. If finish=true, the send-side is closed after the
// buffer is delivered. onSent fires once the transport has accepted
// the buffer (native) or the WritableStream writer has resolved
// (browser). Available on both native and browser builds.
void SendAsync(const void* buffer, std::uint32_t size, bool finish,
std::function<void()> onSent);
// Async receive variants. Dispatched on Crafter.Thread's ThreadPool
// (native) or driven by a per-stream JS reader loop (browser).
2026-05-06 04:06:17 +02:00
void RecieveAsync(std::function<void(std::vector<char>)> recieveCallback);
void RecieveUntilCloseAsync(std::function<void(std::vector<char>)> recieveCallback);
void RecieveUntilFullAsync(std::uint32_t bufferSize, std::function<void(std::vector<char>)> recieveCallback);
2026-05-19 02:53:50 +02:00
#ifndef CRAFTER_NETWORK_BROWSER
// Advanced: re-inject already-consumed bytes at the front of the
// receive queue so the next Recieve* call sees them. Used by
// protocol demuxers (e.g. the WebTransport stream router in
// ListenerHTTP) that need to peek a prefix off the wire, then hand
// the stream to user code as if the prefix had never been read.
void PrependReceived(std::vector<char> bytes);
// Underlying QUIC stream id. Stable for the stream's lifetime.
// Browsers identify WT streams by the session's CONNECT stream id,
// so the server has to query and remember it at session creation
// time. Throws if the stream is not yet started.
std::uint64_t GetStreamId() const;
#endif
2026-05-06 04:06:17 +02:00
// Cleanly shut down the stream (both directions).
void Stop();
private:
struct Impl;
std::unique_ptr<Impl> impl;
friend class ClientQUIC;
};
// A QUIC connection. On the client side, constructing one initiates the
2026-05-19 02:53:50 +02:00
// handshake and (on native) blocks until it succeeds, or throws on
// failure. On the server side, ListenerQUIC instantiates these for
// accepted peers.
2026-05-06 04:06:17 +02:00
//
// A connection multiplexes:
// - Reliable, ordered streams (open via OpenStream() / observe inbound
// via OnStream()).
// - Unreliable, unordered datagrams (SendDatagram() / OnDatagram()).
//
// Lifetime: ~ClientQUIC closes the connection. Streams obtained from
// OpenStream() are scoped to the connection and must be destroyed (or
// moved out) before the ClientQUIC.
2026-05-19 02:53:50 +02:00
//
// Browser build: the only QUIC-shaped API the browser exposes is
// WebTransport, which is HTTP/3-based and reached at a fixed URL. Here:
// - The constructor returns immediately; the connection is opened in
// the background. Operations issued before the connection is ready
// are queued JS-side until WebTransport's "ready" promise resolves
// (or fail with QUICClosedException if the connection rejects).
// - `alpn` is mapped to the URL path: new WebTransport(
// `https://${host}:${port}/${alpn}`). The QUIC-layer ALPN itself
// is fixed to "h3" by the browser and cannot be customised.
// - The server side must accept WebTransport sessions (HTTP/3 extended
// CONNECT) on the path equal to `alpn`. Plain QUIC with a custom
// ALPN — what ListenerQUIC offers today — is not reachable from a
// browser.
// - Synchronous send/receive methods are not compiled. Use the *Async
// variants instead.
2026-05-06 04:06:17 +02:00
export class ClientQUIC {
public:
// ALPN identifier exchanged in the handshake. Both peers must agree.
// For 3DForts use e.g. "f3d/1" or similar — a short stable token.
2026-05-19 02:53:50 +02:00
// On the browser build, this is the WebTransport URL path instead
// of an ALPN token; see the class comment above.
2026-05-06 04:06:17 +02:00
std::string alpn;
// Client constructor: connects to host:port using QUIC. ALPN must
// match the listener. Throws QUICException on connect failure.
ClientQUIC(const char* host, std::uint16_t port, std::string alpn,
QUICClientCredentials creds = {});
ClientQUIC(std::string host, std::uint16_t port, std::string alpn,
QUICClientCredentials creds = {});
2026-05-19 02:53:50 +02:00
#ifndef CRAFTER_NETWORK_BROWSER
2026-05-06 04:06:17 +02:00
// Server-side constructor used by ListenerQUIC for accepted peers.
// Takes ownership of an already-accepted msquic connection handle
// and the server configuration handle. Not intended for direct use.
ClientQUIC(HQUIC connectionHandle, HQUIC serverConfiguration, std::string alpn);
2026-05-19 02:53:50 +02:00
#endif
2026-05-06 04:06:17 +02:00
~ClientQUIC();
ClientQUIC(const ClientQUIC&) = delete;
ClientQUIC(ClientQUIC&&) noexcept;
2026-05-07 00:06:44 +02:00
// Open a new stream initiated by this side. Defaults to bidirectional;
// pass unidirectional=true to open a one-way send stream (used for
// HTTP/3's control + QPACK encoder/decoder streams).
2026-05-06 04:06:17 +02:00
// Blocks until the stream is started; throws on failure.
2026-05-07 00:06:44 +02:00
QUICStream OpenStream(bool unidirectional = false);
2026-05-06 04:06:17 +02:00
// Send a datagram. Best-effort: may be silently dropped under loss
// or congestion. Size must fit within the path MTU (msquic surfaces
// the maximum via QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED — typically
// ~1200 bytes safely on the open internet).
void SendDatagram(const void* buffer, std::uint32_t size);
// Register a handler for streams the peer initiates toward us.
// Called on the msquic worker; offload heavy work to ThreadPool.
void OnStream(std::function<void(QUICStream)> callback);
// Register a handler for datagrams from the peer. Called on the
// msquic worker; copy/queue and return promptly.
void OnDatagram(std::function<void(std::vector<char>)> callback);
2026-05-19 02:53:50 +02:00
#ifndef CRAFTER_NETWORK_BROWSER
2026-05-06 04:06:17 +02:00
// Block the caller until the next datagram arrives; returns it.
// Throws QUICClosedException if the connection closes first.
std::vector<char> RecieveDatagramSync();
2026-05-19 02:53:50 +02:00
#endif
2026-05-06 04:06:17 +02:00
// Cleanly shut down the connection.
void Stop();
2026-05-19 02:53:50 +02:00
#ifndef CRAFTER_NETWORK_BROWSER
2026-05-06 04:06:17 +02:00
// Underlying handle for advanced use (parameter queries, etc.).
HQUIC GetHandle() const;
2026-05-19 02:53:50 +02:00
#endif
2026-05-06 04:06:17 +02:00
private:
struct Impl;
std::unique_ptr<Impl> impl;
friend class ListenerQUIC;
friend class QUICStream;
};
}