130 lines
5 KiB
C++
130 lines
5 KiB
C++
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
// QUICSettings has to actually reach the msquic Configuration. The knob with
|
||
|
|
// observable behaviour is peerUnidiStreamCount: it caps how many streams the
|
||
|
|
// peer may open toward us, so the listener here opens more unidi streams than
|
||
|
|
// the previously hardcoded limit of 16 allowed. Against a build that ignores
|
||
|
|
// the setting the extra streams never arrive and this test times out.
|
||
|
|
|
||
|
|
import Crafter.Network;
|
||
|
|
import Crafter.Thread;
|
||
|
|
import std;
|
||
|
|
using namespace Crafter;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
int g_failures = 0;
|
||
|
|
void Check(bool cond, std::string_view what) {
|
||
|
|
std::println(" {}: {}", cond ? "ok" : "FAIL", what);
|
||
|
|
if (!cond) ++g_failures;
|
||
|
|
}
|
||
|
|
|
||
|
|
constexpr std::uint16_t port = 9187;
|
||
|
|
constexpr std::string_view alpn = "f3d/test-settings";
|
||
|
|
|
||
|
|
// Above the old hardcoded cap of 16, below the 32 we grant below.
|
||
|
|
constexpr std::size_t streamCount = 24;
|
||
|
|
}
|
||
|
|
|
||
|
|
int main() {
|
||
|
|
ThreadPool::Start();
|
||
|
|
|
||
|
|
std::mutex mtx;
|
||
|
|
std::condition_variable cv;
|
||
|
|
std::set<int> received;
|
||
|
|
|
||
|
|
QUICServerCredentials serverCreds;
|
||
|
|
serverCreds.selfSigned = true;
|
||
|
|
|
||
|
|
// The server-side streams must outlive their SendSync, so park them here
|
||
|
|
// rather than letting them destruct at the end of each loop iteration.
|
||
|
|
std::mutex serverMtx;
|
||
|
|
std::vector<QUICStream> serverStreams;
|
||
|
|
serverStreams.reserve(streamCount);
|
||
|
|
std::string openError;
|
||
|
|
|
||
|
|
ListenerQUIC listener(port, std::string(alpn), serverCreds, [&](ClientQUIC* peer) {
|
||
|
|
std::vector<QUICStream> opened;
|
||
|
|
opened.reserve(streamCount);
|
||
|
|
try {
|
||
|
|
// Open every stream *before* sending on any of them, so all of
|
||
|
|
// them are live at once and the peer's unidi credit has to cover
|
||
|
|
// the whole count simultaneously. Opening and finishing one at a
|
||
|
|
// time recycles credit as each stream closes, which passes even
|
||
|
|
// at the old cap of 16 and would test nothing.
|
||
|
|
for (std::size_t i = 0; i < streamCount; ++i) {
|
||
|
|
opened.push_back(peer->OpenStream(/*unidirectional=*/true));
|
||
|
|
}
|
||
|
|
// Leave the send side open — a finished stream returns its credit.
|
||
|
|
for (std::size_t i = 0; i < streamCount; ++i) {
|
||
|
|
std::string message = std::format("{}", i);
|
||
|
|
opened[i].SendSync(message.data(), static_cast<std::uint32_t>(message.size()),
|
||
|
|
/*finish=*/false);
|
||
|
|
}
|
||
|
|
} catch (std::exception& e) {
|
||
|
|
std::lock_guard lock(serverMtx);
|
||
|
|
openError = e.what();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
std::lock_guard lock(serverMtx);
|
||
|
|
serverStreams = std::move(opened);
|
||
|
|
});
|
||
|
|
listener.ListenAsyncAsync();
|
||
|
|
|
||
|
|
try {
|
||
|
|
QUICClientCredentials clientCreds;
|
||
|
|
clientCreds.insecureNoServerValidation = true;
|
||
|
|
// The knob under test. Without it this defaults to 16 and the
|
||
|
|
// listener stalls partway through its 24 streams.
|
||
|
|
clientCreds.settings.peerUnidiStreamCount = 32;
|
||
|
|
|
||
|
|
ClientQUIC client(std::string("localhost"), port, std::string(alpn), clientCreds);
|
||
|
|
|
||
|
|
// Streams opened before this callback lands are queued and drained
|
||
|
|
// into it, so registering after connect is safe.
|
||
|
|
std::mutex clientMtx;
|
||
|
|
std::vector<QUICStream> clientStreams;
|
||
|
|
clientStreams.reserve(streamCount);
|
||
|
|
client.OnStream([&](QUICStream stream) {
|
||
|
|
try {
|
||
|
|
// One chunk only: the peer deliberately leaves the send
|
||
|
|
// side open so the stream keeps holding its credit.
|
||
|
|
std::vector<char> got = stream.RecieveSync();
|
||
|
|
int index = std::stoi(std::string(got.begin(), got.end()));
|
||
|
|
{
|
||
|
|
std::lock_guard lock(mtx);
|
||
|
|
received.insert(index);
|
||
|
|
}
|
||
|
|
cv.notify_all();
|
||
|
|
std::lock_guard lock(clientMtx);
|
||
|
|
clientStreams.push_back(std::move(stream));
|
||
|
|
} catch (...) {
|
||
|
|
// A stream that fails simply never counts toward the total.
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
std::size_t got = 0;
|
||
|
|
{
|
||
|
|
std::unique_lock lock(mtx);
|
||
|
|
cv.wait_for(lock, std::chrono::seconds(20),
|
||
|
|
[&] { return received.size() == streamCount; });
|
||
|
|
got = received.size();
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
std::lock_guard lock(serverMtx);
|
||
|
|
Check(openError.empty(),
|
||
|
|
openError.empty() ? "listener opened every stream"
|
||
|
|
: std::format("listener failed to open: {}", openError));
|
||
|
|
}
|
||
|
|
Check(got == streamCount,
|
||
|
|
std::format("received {} of {} peer-initiated unidi streams", got, streamCount));
|
||
|
|
|
||
|
|
// msquic's RegistrationClose blocks on connections the listener still
|
||
|
|
// owns, so skip the static-dtor path the way the other QUIC tests do.
|
||
|
|
std::_Exit(g_failures ? 1 : 0);
|
||
|
|
} catch (std::exception& e) {
|
||
|
|
std::println("{}", e.what());
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
}
|