/* 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; 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 onStream; std::deque pendingStreams; std::function)> onDatagram; // Phase 2 bool closed = false; }; WebTransportSession::WebTransportSession() : impl(std::make_unique()) {} 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(prefix.size()), /*finish=*/false); return stream; } void WebTransportSession::OnStream(std::function callback) { std::deque 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)> 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 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; }); } }