135 lines
4.8 KiB
C++
135 lines
4.8 KiB
C++
//SPDX-License-Identifier: LGPL-3.0-only
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
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;
|
|
});
|
|
}
|
|
}
|