full QUIC support
This commit is contained in:
parent
45479a46ff
commit
28fab2509b
18 changed files with 1334 additions and 645 deletions
|
|
@ -150,6 +150,8 @@ struct QUICStream::Impl {
|
|||
}
|
||||
};
|
||||
|
||||
QUICStream::QUICStream() = default;
|
||||
|
||||
QUICStream::QUICStream(HQUIC handle, ClientQUIC* connection)
|
||||
: handle(handle), connection(connection), impl(std::make_unique<Impl>())
|
||||
{
|
||||
|
|
@ -159,7 +161,9 @@ QUICStream::QUICStream(HQUIC handle, ClientQUIC* connection)
|
|||
}
|
||||
|
||||
QUICStream::QUICStream(QUICStream&& other) noexcept
|
||||
: handle(other.handle), connection(other.connection), impl(std::move(other.impl))
|
||||
: handle(other.handle), connection(other.connection),
|
||||
canSend(other.canSend), canReceive(other.canReceive),
|
||||
impl(std::move(other.impl))
|
||||
{
|
||||
other.handle = nullptr;
|
||||
other.connection = nullptr;
|
||||
|
|
@ -170,6 +174,8 @@ QUICStream& QUICStream::operator=(QUICStream&& other) noexcept {
|
|||
Stop();
|
||||
handle = other.handle;
|
||||
connection = other.connection;
|
||||
canSend = other.canSend;
|
||||
canReceive = other.canReceive;
|
||||
impl = std::move(other.impl);
|
||||
other.handle = nullptr;
|
||||
other.connection = nullptr;
|
||||
|
|
@ -183,12 +189,26 @@ QUICStream::~QUICStream() {
|
|||
|
||||
void QUICStream::Stop() {
|
||||
if (!handle) return;
|
||||
Runtime().api->StreamShutdown(handle, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0);
|
||||
// If the stream's SHUTDOWN_COMPLETE event has already fired, msquic has
|
||||
// internally called StreamClose for us (see Impl::Callback) and the
|
||||
// handle is no longer valid — calling StreamShutdown on it trips a
|
||||
// quic_bugcheck inside msquic. Skip in that case. This is the common
|
||||
// path for short-lived request/response streams where both peers FIN
|
||||
// before the wrapper is destroyed.
|
||||
bool alreadyClosed = false;
|
||||
if (impl) {
|
||||
std::lock_guard lk(impl->mtx);
|
||||
alreadyClosed = impl->shutdownComplete;
|
||||
}
|
||||
if (!alreadyClosed) {
|
||||
Runtime().api->StreamShutdown(handle, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0);
|
||||
}
|
||||
handle = nullptr;
|
||||
if (impl) impl->handle = nullptr;
|
||||
}
|
||||
|
||||
void QUICStream::SendSync(const void* buffer, std::uint32_t size, bool finish) {
|
||||
if (!handle) throw QUICClosedException();
|
||||
if (!handle || !canSend) throw QUICClosedException();
|
||||
auto* copy = new char[size];
|
||||
std::memcpy(copy, buffer, size);
|
||||
QUIC_BUFFER quicBuf{};
|
||||
|
|
@ -210,7 +230,7 @@ void QUICStream::SendSync(const void* buffer, std::uint32_t size, bool finish) {
|
|||
}
|
||||
|
||||
std::vector<char> QUICStream::RecieveSync() {
|
||||
if (!handle) throw QUICClosedException();
|
||||
if (!handle || !canReceive) throw QUICClosedException();
|
||||
std::unique_lock lk(impl->mtx);
|
||||
impl->cv.wait(lk, [&]{ return !impl->pending.empty() || impl->peerSendClosed || impl->shutdownComplete; });
|
||||
if (!impl->pending.empty()) {
|
||||
|
|
@ -222,7 +242,7 @@ std::vector<char> QUICStream::RecieveSync() {
|
|||
}
|
||||
|
||||
std::vector<char> QUICStream::RecieveUntilCloseSync() {
|
||||
if (!handle) throw QUICClosedException();
|
||||
if (!handle || !canReceive) throw QUICClosedException();
|
||||
std::vector<char> out;
|
||||
while (true) {
|
||||
std::unique_lock lk(impl->mtx);
|
||||
|
|
@ -237,7 +257,7 @@ std::vector<char> QUICStream::RecieveUntilCloseSync() {
|
|||
}
|
||||
|
||||
std::vector<char> QUICStream::RecieveUntilFullSync(std::uint32_t bufferSize) {
|
||||
if (!handle) throw QUICClosedException();
|
||||
if (!handle || !canReceive) throw QUICClosedException();
|
||||
std::vector<char> out;
|
||||
out.reserve(bufferSize);
|
||||
while (out.size() < bufferSize) {
|
||||
|
|
@ -285,6 +305,12 @@ struct ClientQUIC::Impl {
|
|||
std::function<void(QUICStream)> onStream;
|
||||
std::function<void(std::vector<char>)> onDatagram;
|
||||
std::deque<std::vector<char>> datagramQueue;
|
||||
// Streams the peer started before the user installed an OnStream
|
||||
// handler. Without this backlog the early streams (e.g. an h3 server's
|
||||
// control stream right after handshake) would be aborted in the
|
||||
// PEER_STREAM_STARTED branch and the connection would die with
|
||||
// H3_MISSING_SETTINGS on the peer side.
|
||||
std::deque<QUICStream> pendingStreams;
|
||||
|
||||
ClientQUIC* outer = nullptr;
|
||||
|
||||
|
|
@ -325,18 +351,30 @@ struct ClientQUIC::Impl {
|
|||
}
|
||||
case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED: {
|
||||
HQUIC streamHandle = ev->PEER_STREAM_STARTED.Stream;
|
||||
bool unidirectional = (ev->PEER_STREAM_STARTED.Flags
|
||||
& QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL) != 0;
|
||||
QUICStream stream(streamHandle, self->outer);
|
||||
if (self->onStream) {
|
||||
auto cb = self->onStream;
|
||||
auto* shared = new QUICStream(std::move(stream));
|
||||
ThreadPool::Enqueue([cb, shared]{
|
||||
cb(std::move(*shared));
|
||||
delete shared;
|
||||
});
|
||||
} else {
|
||||
// No handler: shut down to avoid leaking a stream.
|
||||
Runtime().api->StreamShutdown(streamHandle, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0);
|
||||
if (unidirectional) {
|
||||
// Peer-initiated unidi: peer sends, we read; we cannot send.
|
||||
stream.canSend = false;
|
||||
stream.canReceive = true;
|
||||
}
|
||||
std::function<void(QUICStream)> cb;
|
||||
{
|
||||
std::lock_guard lk(self->mtx);
|
||||
cb = self->onStream;
|
||||
if (!cb) {
|
||||
// Buffer until OnStream is installed; OnStream's
|
||||
// setter drains this queue.
|
||||
self->pendingStreams.push_back(std::move(stream));
|
||||
return QUIC_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
auto* shared = new QUICStream(std::move(stream));
|
||||
ThreadPool::Enqueue([cb, shared]{
|
||||
cb(std::move(*shared));
|
||||
delete shared;
|
||||
});
|
||||
return QUIC_STATUS_SUCCESS;
|
||||
}
|
||||
case QUIC_CONNECTION_EVENT_DATAGRAM_RECEIVED: {
|
||||
|
|
@ -382,6 +420,16 @@ static HQUIC OpenClientConfiguration(const std::string& alpn, const QUICClientCr
|
|||
settings.IdleTimeoutMs = 30'000;
|
||||
settings.IsSet.DatagramReceiveEnabled = 1;
|
||||
settings.DatagramReceiveEnabled = 1;
|
||||
// Allow the server to open unidi/bidi streams to us. msquic defaults
|
||||
// both peer-stream-count limits to 0; with that, the server's HTTP/3
|
||||
// control stream + QPACK encoder/decoder streams can't be created and
|
||||
// most h3 servers will close the connection after handshake. We don't
|
||||
// currently use server push (h3 pushes ride on unidi 0x01 streams) but
|
||||
// the bidi cap is harmless to grant.
|
||||
settings.IsSet.PeerUnidiStreamCount = 1;
|
||||
settings.PeerUnidiStreamCount = 16;
|
||||
settings.IsSet.PeerBidiStreamCount = 1;
|
||||
settings.PeerBidiStreamCount = 16;
|
||||
|
||||
HQUIC cfg = nullptr;
|
||||
QUIC_STATUS s = Runtime().api->ConfigurationOpen(Runtime().registration, &alpnBuffer, 1,
|
||||
|
|
@ -470,18 +518,26 @@ void ClientQUIC::Stop() {
|
|||
Runtime().api->ConnectionShutdown(impl->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0);
|
||||
}
|
||||
|
||||
QUICStream ClientQUIC::OpenStream() {
|
||||
QUICStream ClientQUIC::OpenStream(bool unidirectional) {
|
||||
HQUIC streamHandle = nullptr;
|
||||
QUICStream stream;
|
||||
stream.impl = std::make_unique<QUICStream::Impl>();
|
||||
stream.impl->connection = this;
|
||||
QUIC_STATUS s = Runtime().api->StreamOpen(impl->connection, QUIC_STREAM_OPEN_FLAG_NONE,
|
||||
QUIC_STREAM_OPEN_FLAGS openFlags = unidirectional
|
||||
? QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL
|
||||
: QUIC_STREAM_OPEN_FLAG_NONE;
|
||||
QUIC_STATUS s = Runtime().api->StreamOpen(impl->connection, openFlags,
|
||||
reinterpret_cast<QUIC_STREAM_CALLBACK_HANDLER>(&QUICStream::Impl::Callback),
|
||||
stream.impl.get(), &streamHandle);
|
||||
if (QUIC_FAILED(s)) throw QUICException(std::format("StreamOpen failed: 0x{:x}", static_cast<unsigned>(s)));
|
||||
stream.handle = streamHandle;
|
||||
stream.connection = this;
|
||||
stream.impl->handle = streamHandle;
|
||||
if (unidirectional) {
|
||||
// We initiated the unidi stream: we send, peer reads.
|
||||
stream.canSend = true;
|
||||
stream.canReceive = false;
|
||||
}
|
||||
s = Runtime().api->StreamStart(streamHandle, QUIC_STREAM_START_FLAG_NONE);
|
||||
if (QUIC_FAILED(s)) {
|
||||
Runtime().api->StreamClose(streamHandle);
|
||||
|
|
@ -510,7 +566,21 @@ void ClientQUIC::SendDatagram(const void* buffer, std::uint32_t size) {
|
|||
}
|
||||
|
||||
void ClientQUIC::OnStream(std::function<void(QUICStream)> cb) {
|
||||
impl->onStream = std::move(cb);
|
||||
std::deque<QUICStream> backlog;
|
||||
{
|
||||
std::lock_guard lk(impl->mtx);
|
||||
impl->onStream = cb;
|
||||
std::swap(backlog, impl->pendingStreams);
|
||||
}
|
||||
while (!backlog.empty()) {
|
||||
auto* shared = new QUICStream(std::move(backlog.front()));
|
||||
backlog.pop_front();
|
||||
auto handler = cb;
|
||||
ThreadPool::Enqueue([handler, shared]{
|
||||
handler(std::move(*shared));
|
||||
delete shared;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ClientQUIC::OnDatagram(std::function<void(std::vector<char>)> cb) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue