//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 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 serverStreams; serverStreams.reserve(streamCount); std::string openError; ListenerQUIC listener(port, std::string(alpn), serverCreds, [&](ClientQUIC* peer) { std::vector 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(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 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 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; } }