browser wasm
This commit is contained in:
parent
28fab2509b
commit
e8630528af
24 changed files with 2490 additions and 100 deletions
|
|
@ -32,6 +32,12 @@ namespace Crafter {
|
|||
//
|
||||
// For local development against a self-signed listener, pass
|
||||
// QUICClientCredentials{insecureNoServerValidation = true}.
|
||||
//
|
||||
// Browser build: the request is dispatched via the browser's fetch()
|
||||
// and the synchronous Send() is not compiled — use SendAsync instead.
|
||||
// The ClientHTTP instance does not maintain a persistent connection
|
||||
// (fetch is request-scoped); host and port are stored and prefixed to
|
||||
// the request path on each call. QUICClientCredentials is ignored.
|
||||
export class ClientHTTP {
|
||||
public:
|
||||
std::string host;
|
||||
|
|
@ -44,8 +50,18 @@ namespace Crafter {
|
|||
ClientHTTP(const ClientHTTP&) = delete;
|
||||
ClientHTTP(ClientHTTP&&) noexcept;
|
||||
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
// Send a request and synchronously read back the full response.
|
||||
HTTPResponse Send(const HTTPRequest& request);
|
||||
#endif
|
||||
|
||||
// Send a request and deliver the response (or an error) via callback.
|
||||
// Available on both native and browser builds. Native dispatches on
|
||||
// Crafter.Thread's ThreadPool; browser uses fetch() and resolves on
|
||||
// the JS event loop.
|
||||
void SendAsync(const HTTPRequest& request,
|
||||
std::function<void(HTTPResponse)> onSuccess,
|
||||
std::function<void(std::string)> onError);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
*/
|
||||
|
||||
module;
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
#include <msquic.h>
|
||||
#endif
|
||||
export module Crafter.Network:ClientQUIC;
|
||||
import std;
|
||||
|
||||
|
|
@ -45,9 +47,16 @@ namespace Crafter {
|
|||
};
|
||||
|
||||
// Client-side credential validation. By default we require a real cert.
|
||||
// insecureNoServerValidation disables peer cert checks — only for dev.
|
||||
// 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.
|
||||
export struct QUICClientCredentials {
|
||||
bool insecureNoServerValidation = false;
|
||||
std::array<std::uint8_t, 32> serverCertificateHash{};
|
||||
};
|
||||
|
||||
export class ClientQUIC;
|
||||
|
|
@ -60,8 +69,10 @@ namespace Crafter {
|
|||
// for inbound streams initiated by the peer.
|
||||
export class QUICStream {
|
||||
public:
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
// Underlying msquic HQUIC handle. Treated as opaque by callers.
|
||||
HQUIC handle = nullptr;
|
||||
#endif
|
||||
|
||||
// The connection that owns this stream (non-owning).
|
||||
ClientQUIC* connection = nullptr;
|
||||
|
|
@ -72,12 +83,22 @@ namespace Crafter {
|
|||
bool canReceive = true;
|
||||
|
||||
QUICStream();
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
QUICStream(HQUIC handle, ClientQUIC* connection);
|
||||
#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
|
||||
~QUICStream();
|
||||
QUICStream(const QUICStream&) = delete;
|
||||
QUICStream(QUICStream&&) noexcept;
|
||||
QUICStream& operator=(QUICStream&&) noexcept;
|
||||
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
// 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.
|
||||
|
|
@ -93,12 +114,36 @@ namespace Crafter {
|
|||
|
||||
// Read exactly bufferSize bytes; throws if the peer closes early.
|
||||
std::vector<char> RecieveUntilFullSync(std::uint32_t bufferSize);
|
||||
#endif
|
||||
|
||||
// Async variants: dispatched on Crafter.Thread's ThreadPool.
|
||||
// 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).
|
||||
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);
|
||||
|
||||
#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
|
||||
|
||||
// Cleanly shut down the stream (both directions).
|
||||
void Stop();
|
||||
|
||||
|
|
@ -109,8 +154,9 @@ namespace Crafter {
|
|||
};
|
||||
|
||||
// A QUIC connection. On the client side, constructing one initiates the
|
||||
// handshake and blocks until it succeeds (or throws on failure). On the
|
||||
// server side, ListenerQUIC instantiates these for accepted peers.
|
||||
// handshake and (on native) blocks until it succeeds, or throws on
|
||||
// failure. On the server side, ListenerQUIC instantiates these for
|
||||
// accepted peers.
|
||||
//
|
||||
// A connection multiplexes:
|
||||
// - Reliable, ordered streams (open via OpenStream() / observe inbound
|
||||
|
|
@ -120,10 +166,28 @@ namespace Crafter {
|
|||
// Lifetime: ~ClientQUIC closes the connection. Streams obtained from
|
||||
// OpenStream() are scoped to the connection and must be destroyed (or
|
||||
// moved out) before the ClientQUIC.
|
||||
//
|
||||
// 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.
|
||||
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.
|
||||
// On the browser build, this is the WebTransport URL path instead
|
||||
// of an ALPN token; see the class comment above.
|
||||
std::string alpn;
|
||||
|
||||
// Client constructor: connects to host:port using QUIC. ALPN must
|
||||
|
|
@ -133,10 +197,12 @@ namespace Crafter {
|
|||
ClientQUIC(std::string host, std::uint16_t port, std::string alpn,
|
||||
QUICClientCredentials creds = {});
|
||||
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
// 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);
|
||||
#endif
|
||||
|
||||
~ClientQUIC();
|
||||
ClientQUIC(const ClientQUIC&) = delete;
|
||||
|
|
@ -162,15 +228,19 @@ namespace Crafter {
|
|||
// msquic worker; copy/queue and return promptly.
|
||||
void OnDatagram(std::function<void(std::vector<char>)> callback);
|
||||
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
// Block the caller until the next datagram arrives; returns it.
|
||||
// Throws QUICClosedException if the connection closes first.
|
||||
std::vector<char> RecieveDatagramSync();
|
||||
#endif
|
||||
|
||||
// Cleanly shut down the connection.
|
||||
void Stop();
|
||||
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
// Underlying handle for advanced use (parameter queries, etc.).
|
||||
HQUIC GetHandle() const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ 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;
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
|
@ -31,9 +32,11 @@ module;
|
|||
#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:
|
||||
|
|
@ -68,4 +71,5 @@ namespace Crafter {
|
|||
hostent* host;
|
||||
sockaddr_in serv_addr;
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -43,11 +43,33 @@ namespace Crafter::HTTP3 {
|
|||
export inline constexpr std::uint64_t kFrameData = 0x00;
|
||||
export inline constexpr std::uint64_t kFrameHeaders = 0x01;
|
||||
export inline constexpr std::uint64_t kFrameSettings = 0x04;
|
||||
// WebTransport bidirectional stream frame type (draft-ietf-webtrans-http3).
|
||||
// Distinct from normal HTTP/3 frames — its body is unbounded (runs to FIN)
|
||||
// rather than length-prefixed, and the first bytes of the body are the
|
||||
// session id varint.
|
||||
export inline constexpr std::uint64_t kFrameWtStream = 0x41;
|
||||
|
||||
// ---------------- Unidirectional stream types (RFC 9114 §6.2) ----------------
|
||||
export inline constexpr std::uint64_t kStreamControl = 0x00;
|
||||
export inline constexpr std::uint64_t kStreamQpackEnc = 0x02;
|
||||
export inline constexpr std::uint64_t kStreamQpackDec = 0x03;
|
||||
// WebTransport unidirectional stream type (draft-ietf-webtrans-http3).
|
||||
// After this varint comes a session id varint, then opaque payload to FIN.
|
||||
export inline constexpr std::uint64_t kStreamWt = 0x54;
|
||||
|
||||
// ---------------- SETTINGS parameter identifiers ----------------
|
||||
// Required to negotiate WebTransport over HTTP/3 + HTTP/3 datagrams.
|
||||
export inline constexpr std::uint64_t kSettingQpackMaxTableCapacity = 0x01; // RFC 9204
|
||||
export inline constexpr std::uint64_t kSettingQpackBlockedStreams = 0x07; // RFC 9204
|
||||
export inline constexpr std::uint64_t kSettingEnableConnectProtocol = 0x08; // RFC 9220
|
||||
export inline constexpr std::uint64_t kSettingH3Datagram = 0x33; // RFC 9297
|
||||
// Legacy identifiers from older WebTransport / H3-DATAGRAM drafts. Chrome
|
||||
// (as of M120-ish) advertises and looks for the draft-02 / draft-04 ids
|
||||
// alongside the RFC ones; if we only send the modern ids it decides we
|
||||
// don't support WebTransport and aborts with ERR_METHOD_NOT_SUPPORTED.
|
||||
export inline constexpr std::uint64_t kSettingH3DatagramDraft04 = 0xffd277; // draft-ietf-masque-h3-datagram-04
|
||||
export inline constexpr std::uint64_t kSettingEnableWebTransport = 0x2b603742; // draft-02 boolean
|
||||
export inline constexpr std::uint64_t kSettingWtMaxSessions = 0xc671706a; // draft-ietf-webtrans-http3 (-07+)
|
||||
|
||||
// ---------------- Errors ----------------
|
||||
export class HTTP3ProtocolError : public std::runtime_error {
|
||||
|
|
@ -575,4 +597,55 @@ namespace Crafter::HTTP3 {
|
|||
EncodeVarint(0, out); // frame length 0
|
||||
return out;
|
||||
}
|
||||
|
||||
// Server-side variant that advertises WebTransport-over-HTTP/3 support
|
||||
// to the peer. Without these three SETTINGS the browser silently rejects
|
||||
// the extended CONNECT and the WebTransport.ready promise never resolves.
|
||||
// `maxSessions` becomes the value of SETTINGS_WT_MAX_SESSIONS.
|
||||
export inline std::vector<std::uint8_t> BuildWebTransportControlStreamPrelude(
|
||||
std::uint64_t maxSessions = 1)
|
||||
{
|
||||
// Encode the SETTINGS body first so we can write its length. The two
|
||||
// QPACK settings declare we run with no dynamic table — sent
|
||||
// explicitly because some HTTP/3 stacks (Chrome among them) refuse
|
||||
// to consider the peer ready for extended-CONNECT until they have
|
||||
// seen a baseline QPACK configuration. The draft-02 ENABLE_WEBTRANSPORT
|
||||
// and draft-04 H3_DATAGRAM ids are sent alongside their RFC counterparts
|
||||
// for compatibility with current Chrome (which still negotiates the
|
||||
// draft form even when advertising RFC support).
|
||||
std::vector<std::uint8_t> body;
|
||||
EncodeVarint(kSettingQpackMaxTableCapacity, body); EncodeVarint(0, body);
|
||||
EncodeVarint(kSettingQpackBlockedStreams, body); EncodeVarint(0, body);
|
||||
EncodeVarint(kSettingEnableConnectProtocol, body); EncodeVarint(1, body);
|
||||
EncodeVarint(kSettingH3Datagram, body); EncodeVarint(1, body);
|
||||
EncodeVarint(kSettingH3DatagramDraft04, body); EncodeVarint(1, body);
|
||||
EncodeVarint(kSettingEnableWebTransport, body); EncodeVarint(1, body);
|
||||
EncodeVarint(kSettingWtMaxSessions, body); EncodeVarint(maxSessions, body);
|
||||
|
||||
std::vector<std::uint8_t> out;
|
||||
EncodeVarint(kStreamControl, out);
|
||||
WriteFrame(out, kFrameSettings, body.data(), body.size());
|
||||
return out;
|
||||
}
|
||||
|
||||
// Prefix bytes that go on the front of an outgoing WT bidi stream — the
|
||||
// peer reads these to know which session the stream belongs to. After
|
||||
// this prefix the stream contains opaque WebTransport payload until FIN
|
||||
// (there is no length field — WT_STREAM is the only HTTP/3 frame whose
|
||||
// body runs to end-of-stream).
|
||||
export inline std::vector<std::uint8_t> BuildWtBidiPrefix(std::uint64_t sessionId) {
|
||||
std::vector<std::uint8_t> out;
|
||||
EncodeVarint(kFrameWtStream, out);
|
||||
EncodeVarint(sessionId, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Prefix bytes that go on the front of an outgoing WT unidi stream
|
||||
// (server-initiated → client). Stream-type varint then session id.
|
||||
export inline std::vector<std::uint8_t> BuildWtUnidiPrefix(std::uint64_t sessionId) {
|
||||
std::vector<std::uint8_t> out;
|
||||
EncodeVarint(kStreamWt, out);
|
||||
EncodeVarint(sessionId, out);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ import std;
|
|||
import :HTTP;
|
||||
import :ListenerQUIC;
|
||||
import :ClientQUIC;
|
||||
import :WebTransport;
|
||||
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
namespace Crafter {
|
||||
// HTTP/3 server. Wraps a ListenerQUIC: each accepted QUIC connection
|
||||
// registers a per-stream handler that parses one request, dispatches it
|
||||
|
|
@ -33,6 +35,13 @@ namespace Crafter {
|
|||
// Routes are keyed by `:path` (exact match). Unknown paths return a
|
||||
// synthetic 404. Route handlers run on the ThreadPool — multiple requests
|
||||
// on the same connection can therefore execute concurrently.
|
||||
//
|
||||
// WebTransport: pass a non-empty `wtRoutes` to additionally accept
|
||||
// extended-CONNECT requests (`:method=CONNECT, :protocol=webtransport`)
|
||||
// whose `:path` matches a registered route. The matching handler runs
|
||||
// on the ThreadPool with a `WebTransportSession&` argument scoped to
|
||||
// the session's lifetime. Sending WT-required SETTINGS happens
|
||||
// automatically when wtRoutes is non-empty.
|
||||
export class ListenerHTTP {
|
||||
public:
|
||||
// The underlying QUIC listener owns the accept loop, certificates,
|
||||
|
|
@ -40,12 +49,20 @@ namespace Crafter {
|
|||
// and owned by this Impl so that move construction/destruction is
|
||||
// straightforward.
|
||||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes;
|
||||
std::unordered_map<std::string, std::function<void(WebTransportSession&)>> wtRoutes;
|
||||
std::string alpn;
|
||||
|
||||
ListenerHTTP(std::uint16_t port,
|
||||
QUICServerCredentials creds,
|
||||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes);
|
||||
|
||||
// WT-aware overload. `routes` and `wtRoutes` may both be non-empty;
|
||||
// they are dispatched on disjoint criteria so they don't collide.
|
||||
ListenerHTTP(std::uint16_t port,
|
||||
QUICServerCredentials creds,
|
||||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes,
|
||||
std::unordered_map<std::string, std::function<void(WebTransportSession&)>> wtRoutes);
|
||||
|
||||
~ListenerHTTP();
|
||||
ListenerHTTP(const ListenerHTTP&) = delete;
|
||||
ListenerHTTP(ListenerHTTP&&) noexcept;
|
||||
|
|
@ -71,7 +88,15 @@ namespace Crafter {
|
|||
ListenerAsyncHTTP(std::uint16_t port,
|
||||
QUICServerCredentials creds,
|
||||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes);
|
||||
|
||||
// WT-aware overload.
|
||||
ListenerAsyncHTTP(std::uint16_t port,
|
||||
QUICServerCredentials creds,
|
||||
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes,
|
||||
std::unordered_map<std::string, std::function<void(WebTransportSession&)>> wtRoutes);
|
||||
|
||||
~ListenerAsyncHTTP();
|
||||
void Stop();
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -19,11 +19,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
*/
|
||||
|
||||
module;
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
#include <msquic.h>
|
||||
#endif
|
||||
export module Crafter.Network:ListenerQUIC;
|
||||
import std;
|
||||
import :ClientQUIC;
|
||||
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
namespace Crafter {
|
||||
// Server side of a QUIC connection. Mirrors ListenerTCP in shape:
|
||||
// four Listen* methods covering the sync/async outer-loop x sync/async
|
||||
|
|
@ -72,4 +75,17 @@ namespace Crafter {
|
|||
std::unique_ptr<Impl> impl;
|
||||
std::uint32_t totalClientCounter = 0;
|
||||
};
|
||||
|
||||
// Compute the SHA-256 of the DER bytes of a PEM-encoded X.509 certificate.
|
||||
// Returns the 32-byte digest. Intended for surfacing the self-signed cert
|
||||
// hash to a browser peer (Chrome's WebTransport requires the client to
|
||||
// pass this hash via `serverCertificateHashes` when peering against a
|
||||
// cert that's not in the system trust store). Shells out to openssl.
|
||||
export std::array<std::uint8_t, 32> ComputeCertificateHashSHA256(const std::string& certPath);
|
||||
|
||||
// Path of the lazily-generated self-signed cert (PEM). Triggers generation
|
||||
// on first call. Useful for piping into ComputeCertificateHashSHA256 so
|
||||
// a browser peer can be told the hash to put in `serverCertificateHashes`.
|
||||
export std::string GetSelfSignedCertificatePath();
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export module Crafter.Network:ListenerTCP;
|
|||
import std;
|
||||
import :ClientTCP;
|
||||
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
namespace Crafter {
|
||||
export class ListenerTCP {
|
||||
public:
|
||||
|
|
@ -40,4 +41,5 @@ namespace Crafter {
|
|||
std::uint32_t totalClientCounter = 0;
|
||||
int s;
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
105
interfaces/Crafter.Network-WebTransport.cppm
Normal file
105
interfaces/Crafter.Network-WebTransport.cppm
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
export module Crafter.Network:WebTransport;
|
||||
import std;
|
||||
import :ClientQUIC;
|
||||
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
namespace Crafter {
|
||||
// Server-side handle to one accepted WebTransport-over-HTTP/3 session.
|
||||
// Constructed by ListenerHTTP when it receives an extended-CONNECT
|
||||
// request whose :path matches a registered WT route. Handed to the
|
||||
// user's route handler as the only argument.
|
||||
//
|
||||
// API shape mirrors ClientQUIC so application code can be written once
|
||||
// and used on either side of the wire — open bidi streams, register an
|
||||
// OnStream handler for peer-initiated streams, close the session.
|
||||
//
|
||||
// Lifetime: the session owns the CONNECT stream that was upgraded.
|
||||
// Destruction (or explicit Close()) FINs that stream, which the peer
|
||||
// interprets as session-end. Phase 1 does not emit a CLOSE_WEBTRANSPORT
|
||||
// _SESSION capsule — bare FIN is sufficient for Chrome / Firefox.
|
||||
//
|
||||
// Phase 1 scope:
|
||||
// - bidirectional streams: OpenStream + OnStream
|
||||
// - session close via Close() / destruction
|
||||
// Out of scope (later phases):
|
||||
// - datagrams (SendDatagram / OnDatagram are stubs that no-op)
|
||||
// - unidirectional streams (OpenStream(unidirectional=true) throws)
|
||||
// - capsule protocol (DRAIN/CLOSE capsules)
|
||||
export class WebTransportSession {
|
||||
public:
|
||||
// Underlying QUIC stream id of the CONNECT stream. The peer
|
||||
// identifies streams that belong to this session by this number.
|
||||
std::uint64_t sessionId = 0;
|
||||
|
||||
// Path the client connected to. Useful for routing within a single
|
||||
// wtRoutes handler that's registered against multiple paths.
|
||||
std::string path;
|
||||
|
||||
WebTransportSession();
|
||||
~WebTransportSession();
|
||||
WebTransportSession(const WebTransportSession&) = delete;
|
||||
WebTransportSession(WebTransportSession&&) noexcept;
|
||||
WebTransportSession& operator=(WebTransportSession&&) noexcept;
|
||||
|
||||
// Open a new bidi stream toward the peer. The WT_STREAM prefix
|
||||
// (frame type + session id) is written to the stream automatically
|
||||
// before this returns; the caller's first Send* delivers the first
|
||||
// bytes of opaque payload. Throws on connection close.
|
||||
QUICStream OpenStream(bool unidirectional = false);
|
||||
|
||||
// Register a handler for streams the peer opens against this
|
||||
// session. Already-buffered streams that arrived before the
|
||||
// handler was installed are drained into the new handler.
|
||||
void OnStream(std::function<void(QUICStream)> callback);
|
||||
|
||||
// Register a handler for datagrams the peer sends on this
|
||||
// session. Phase 1 STUB — datagrams are not yet plumbed through.
|
||||
void OnDatagram(std::function<void(std::vector<char>)> callback);
|
||||
|
||||
// Send a datagram. Phase 1 STUB — silently drops.
|
||||
void SendDatagram(const void* buffer, std::uint32_t size);
|
||||
|
||||
// FIN the CONNECT stream. Subsequent OpenStream calls throw; any
|
||||
// pending receivers on owned streams will fail with the connection
|
||||
// close. Idempotent.
|
||||
void Close();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
friend class ListenerHTTP;
|
||||
friend void WebTransportInitialise(WebTransportSession&, ClientQUIC*, QUICStream,
|
||||
std::uint64_t, std::string);
|
||||
friend void WebTransportDeliverStream(WebTransportSession&, QUICStream);
|
||||
};
|
||||
|
||||
// Internal — used by ListenerHTTP's WT demuxer. Not exported (and only
|
||||
// visible to other TUs within the Crafter.Network module).
|
||||
void WebTransportInitialise(WebTransportSession& session,
|
||||
ClientQUIC* connection,
|
||||
QUICStream connectStream,
|
||||
std::uint64_t sessionId,
|
||||
std::string path);
|
||||
void WebTransportDeliverStream(WebTransportSession& session, QUICStream stream);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -26,4 +26,12 @@ export import :ClientHTTP;
|
|||
export import :ListenerHTTP;
|
||||
export import :HTTP;
|
||||
export import :ClientQUIC;
|
||||
export import :ListenerQUIC;
|
||||
export import :ListenerQUIC;
|
||||
export import :WebTransport;
|
||||
#ifndef CRAFTER_NETWORK_BROWSER
|
||||
// Exposed so user code can build WebTransport clients by hand against a
|
||||
// ClientQUIC until we ship a ClientWebTransport wrapper. Most callers do
|
||||
// not need the HTTP/3 frame helpers directly. Excluded from the browser
|
||||
// build — HTTP3 uses throw and the wasm target runs with -fno-exceptions.
|
||||
export import :HTTP3;
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue