Crafter.Network/implementations/Crafter.Network-WebTransport.cpp

135 lines
4.8 KiB
C++
Raw Normal View History

2026-07-22 17:55:45 +02:00
//SPDX-License-Identifier: LGPL-3.0-only
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
2026-05-19 02:53:50 +02:00
module;
module Crafter.Network:WebTransport_impl;
import :WebTransport;
import :ClientQUIC;
import :HTTP3;
import Crafter.Thread;
import std;
using namespace Crafter;
struct WebTransportSession::Impl {
// Non-owning. The session is constructed and owned by ListenerHTTP, and
// ListenerHTTP keeps the underlying ClientQUIC alive in its PeerState
// for the duration of the connection — so this pointer is valid as
// long as the session is.
ClientQUIC* connection = nullptr;
// The HTTP/3 extended-CONNECT bidi stream the session was upgraded on.
// Stays open for the session's lifetime. Phase 1 closes it with a bare
// FIN; later phases will emit a CLOSE_WEBTRANSPORT_SESSION capsule.
QUICStream connectStream;
std::mutex mtx;
std::function<void(QUICStream)> onStream;
std::deque<QUICStream> pendingStreams;
std::function<void(std::vector<char>)> onDatagram; // Phase 2
bool closed = false;
};
WebTransportSession::WebTransportSession()
: impl(std::make_unique<Impl>())
{}
WebTransportSession::WebTransportSession(WebTransportSession&&) noexcept = default;
WebTransportSession& WebTransportSession::operator=(WebTransportSession&&) noexcept = default;
WebTransportSession::~WebTransportSession() {
if (impl) Close();
}
QUICStream WebTransportSession::OpenStream(bool unidirectional) {
if (!impl || impl->closed || !impl->connection) {
throw QUICClosedException();
}
QUICStream stream = impl->connection->OpenStream(unidirectional);
auto prefix = unidirectional
? HTTP3::BuildWtUnidiPrefix(sessionId)
: HTTP3::BuildWtBidiPrefix(sessionId);
// Write the WT_STREAM (bidi) or stream-type (unidi) prefix as the
// first send on the stream. The peer reads it to associate this
// stream with our session before treating the rest as opaque payload.
stream.SendSync(prefix.data(), static_cast<std::uint32_t>(prefix.size()), /*finish=*/false);
return stream;
}
void WebTransportSession::OnStream(std::function<void(QUICStream)> callback) {
std::deque<QUICStream> drained;
{
std::lock_guard lk(impl->mtx);
impl->onStream = callback;
drained.swap(impl->pendingStreams);
}
// Dispatch any streams that arrived before the handler was installed.
// Each goes to the ThreadPool so user code runs off the demuxer thread.
for (auto& s : drained) {
auto* shared = new QUICStream(std::move(s));
ThreadPool::Enqueue([callback, shared]{
callback(std::move(*shared));
delete shared;
});
}
}
void WebTransportSession::OnDatagram(std::function<void(std::vector<char>)> callback) {
// Phase 1 stub. Phase 2 will plumb QUIC datagrams through here after
// demuxing on quarter_session_id.
if (impl) impl->onDatagram = std::move(callback);
}
void WebTransportSession::SendDatagram(const void*, std::uint32_t) {
// Phase 1 stub — would prepend quarter_session_id varint and call
// connection->SendDatagram. Drops silently for now.
}
void WebTransportSession::Close() {
if (!impl || impl->closed) return;
impl->closed = true;
try {
// Empty FIN on the CONNECT stream. Chrome / Firefox both treat
// peer-FIN of the CONNECT stream as session-close.
impl->connectStream.SendSync(nullptr, 0, /*finish=*/true);
} catch (...) {
// Connection may already be gone — that's fine.
}
}
// ─── Internal ListenerHTTP-facing helpers ───────────────────────────────
//
// Declared (not exported) in the interface partition so ListenerHTTP_impl
// can call them; defined here. Friendship in WebTransportSession gives
// them access to the private Impl.
namespace Crafter {
void WebTransportInitialise(WebTransportSession& session,
ClientQUIC* connection,
QUICStream connectStream,
std::uint64_t sessionId,
std::string path) {
session.impl->connection = connection;
session.impl->connectStream = std::move(connectStream);
session.sessionId = sessionId;
session.path = std::move(path);
}
void WebTransportDeliverStream(WebTransportSession& session, QUICStream stream) {
std::function<void(QUICStream)> cb;
{
std::lock_guard lk(session.impl->mtx);
cb = session.impl->onStream;
if (!cb) {
session.impl->pendingStreams.push_back(std::move(stream));
return;
}
}
auto* shared = new QUICStream(std::move(stream));
ThreadPool::Enqueue([cb, shared]{
cb(std::move(*shared));
delete shared;
});
}
}