152 lines
5.5 KiB
C++
152 lines
5.5 KiB
C++
|
|
/*
|
||
|
|
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<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;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|