CultLib/native/GameCult.Mesh.Quic.Native/CMakeLists.txt

118 lines
6.9 KiB
Text
Raw Normal View History

cmake_minimum_required(VERSION 3.24)
project(gamecult_mesh_quic_native LANGUAGES CXX)
Pin the Windows MsQuic to the OpenSSL build, and both of its digests Q9 ruled A. The Schannel MsQuic refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12 with QUIC_STATUS_NOT_SUPPORTED, and PKCS12 is the only credential type a provider has on both platforms, so a Windows provider could not open a listener against the runtime this repo was shipping. The same bridge binary passes all twenty smoke checks and the v1 gate against the OpenSSL build, which is what Soul verified and what this now downloads. Two digests, not one. The zip is pinned by FEE9A664 and 19,179,817 bytes; the msquic.dll inside it by C17C6581 and 4,181,856 bytes. The earlier report and the cut map's first record put the zip's digest on the DLL, which is a pin that checks the archive and then ships a file nobody looked at. Sizes ride along with both, because a digest that fails tells you less than a digest and a length that fail together. The CRT is linked statically. A Node or Unity host without the Visual C++ redistributable cannot load a DLL that imports vcruntime140.dll, and Windows reports that as "the specified module could not be found" while naming the module that was found. Confirmed on the rebuild: msquic.dll, CRYPT32, ntdll, KERNEL32, and no CRT import at all. The DLL goes from 83,456 to 302,080 bytes, which is the price of the host not needing anything installed. The Unity plugin's committed msquic.dll is still the Schannel one and this does not touch it; the next build-unity-package.ps1 run is what replaces it, and that is not in this batch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:04:27 +02:00
# Two platforms, one source. Windows takes MsQuic from the pinned OpenSSL NuGet
Make the MsQuic bridge a two-role event queue instead of a Unity client The bridge was one platform, one role and one host: Windows by construction (`windows.h`, `__declspec`, `CryptHashCertificate2`), client-only, and shaped around the managed connector that polls it every 2 ms. Nothing about MsQuic required that; the shape came from having exactly one caller. It now carries a C ABI v2 of twelve exports over a single per-runtime event queue: open a runtime, open listeners and connections on it, open streams and send frames, and take typed events out of one blocking wait. The event struct is 64 bytes described by layout rather than by a shared header, so a host declares it in its own FFI and no header travels with the ABI. `next_event` keeps the v1 two-phase convention — return 2, leave the event at the head, report the size the caller needs — because that is the one part of v1 that was already host-agnostic. The rule that shapes everything else is that nothing calls out. MsQuic's worker threads only push onto the queue and wake that wait, so no host runtime is entered from a thread it does not own, and the bridge needs to know nothing about what is on the other side. That is what makes one binary serve both a Node process through FFI and a Unity player through P/Invoke. Trust moved out. A v2 client connection defers validation, emits the DER as event 3 and answers MsQuic only when the host calls `connection_certificate_complete`; if the host never answers, MsQuic's handshake timeout closes the connection and event 4 says so. The C code no longer has an opinion about certificates — except in the v1 shim, where it must. The five v1 exports are unchanged in signature and behaviour and are now `#ifdef _WIN32`, matching `CanConnect`'s own Windows gate. A v1 client is a private v2 runtime holding one connection, and the pin comparison is the single thing it still decides in C, because that is the contract the shipped Unity package was built against. `windows.h`, `wincrypt.h` and `CryptHashCertificate2` appear nowhere outside that block. Two things the rewrite had to get right that the old shape never exercised. The v1 pin must be attached before `ConnectionStart`, not after it returns: the handshake runs on an MsQuic worker thread and reaches the certificate event immediately on loopback, so a pin applied afterwards loses the race and the connection silently takes the host path nobody is listening on. And teardown must close the connection handles itself — `RegistrationClose` blocks until the app has closed every child handle, and a silent shutdown delivers no shutdown events at all, so leaning on the callbacks to do it deadlocks the caller forever. Both were found by the tests hanging, and both now have a mutation that reproduces them. Verified: the four v1 tests in tests/GameCult.Mesh.Quic.Native.Tests pass unchanged (3 passed, 1 skipped for want of an external endpoint), including the wrong-pin refusal and the departed-client broadcast test. A 15-check FFI smoke drives the whole v2 path — listener, deferred certificate, both sides connected, latest-only stream, one frame with FIN, identical bytes out — and passes on Windows x64 and on Linux x64. Ten source mutations, ten killed, two green no-op controls. One open fork, reported separately and not decided here: the pinned Windows MsQuic is the Schannel build, which refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12 with QUIC_STATUS_NOT_SUPPORTED, so `listener_open` cannot open a provider against it. The map assumed PKCS12 was "one credential type for Schannel and OpenSSL"; the header declares it but the Schannel provider does not implement it. The identical bridge binary passes the full provider smoke against OpenSSL MsQuic 2.5.9, so this is a question about which flavour Windows pins, not about the bridge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 21:03:01 +02:00
# layout (MSQUIC_ROOT); Linux takes it from the extracted libmsquic deb, whose
# headers and library land in unrelated places, so they are named separately.
if(NOT WIN32 AND NOT (CMAKE_SYSTEM_NAME STREQUAL "Linux"))
message(FATAL_ERROR "The CultMesh native QUIC bridge supports Windows x64 and Linux x64")
endif()
add_library(gamecult_mesh_quic_native SHARED cultmesh_quic_native.cpp)
target_compile_features(gamecult_mesh_quic_native PRIVATE cxx_std_20)
# include/cultmesh_quic_native.h is the host-facing contract, not an internal
# detail: the bridge includes it, and so does any consumer that can.
target_include_directories(gamecult_mesh_quic_native PUBLIC include)
Make the MsQuic bridge a two-role event queue instead of a Unity client The bridge was one platform, one role and one host: Windows by construction (`windows.h`, `__declspec`, `CryptHashCertificate2`), client-only, and shaped around the managed connector that polls it every 2 ms. Nothing about MsQuic required that; the shape came from having exactly one caller. It now carries a C ABI v2 of twelve exports over a single per-runtime event queue: open a runtime, open listeners and connections on it, open streams and send frames, and take typed events out of one blocking wait. The event struct is 64 bytes described by layout rather than by a shared header, so a host declares it in its own FFI and no header travels with the ABI. `next_event` keeps the v1 two-phase convention — return 2, leave the event at the head, report the size the caller needs — because that is the one part of v1 that was already host-agnostic. The rule that shapes everything else is that nothing calls out. MsQuic's worker threads only push onto the queue and wake that wait, so no host runtime is entered from a thread it does not own, and the bridge needs to know nothing about what is on the other side. That is what makes one binary serve both a Node process through FFI and a Unity player through P/Invoke. Trust moved out. A v2 client connection defers validation, emits the DER as event 3 and answers MsQuic only when the host calls `connection_certificate_complete`; if the host never answers, MsQuic's handshake timeout closes the connection and event 4 says so. The C code no longer has an opinion about certificates — except in the v1 shim, where it must. The five v1 exports are unchanged in signature and behaviour and are now `#ifdef _WIN32`, matching `CanConnect`'s own Windows gate. A v1 client is a private v2 runtime holding one connection, and the pin comparison is the single thing it still decides in C, because that is the contract the shipped Unity package was built against. `windows.h`, `wincrypt.h` and `CryptHashCertificate2` appear nowhere outside that block. Two things the rewrite had to get right that the old shape never exercised. The v1 pin must be attached before `ConnectionStart`, not after it returns: the handshake runs on an MsQuic worker thread and reaches the certificate event immediately on loopback, so a pin applied afterwards loses the race and the connection silently takes the host path nobody is listening on. And teardown must close the connection handles itself — `RegistrationClose` blocks until the app has closed every child handle, and a silent shutdown delivers no shutdown events at all, so leaning on the callbacks to do it deadlocks the caller forever. Both were found by the tests hanging, and both now have a mutation that reproduces them. Verified: the four v1 tests in tests/GameCult.Mesh.Quic.Native.Tests pass unchanged (3 passed, 1 skipped for want of an external endpoint), including the wrong-pin refusal and the departed-client broadcast test. A 15-check FFI smoke drives the whole v2 path — listener, deferred certificate, both sides connected, latest-only stream, one frame with FIN, identical bytes out — and passes on Windows x64 and on Linux x64. Ten source mutations, ten killed, two green no-op controls. One open fork, reported separately and not decided here: the pinned Windows MsQuic is the Schannel build, which refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12 with QUIC_STATUS_NOT_SUPPORTED, so `listener_open` cannot open a provider against it. The map assumed PKCS12 was "one credential type for Schannel and OpenSSL"; the header declares it but the Schannel provider does not implement it. The identical bridge binary passes the full provider smoke against OpenSSL MsQuic 2.5.9, so this is a question about which flavour Windows pins, not about the bridge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 21:03:01 +02:00
if(WIN32)
if(NOT DEFINED MSQUIC_ROOT)
Pin the Windows MsQuic to the OpenSSL build, and both of its digests Q9 ruled A. The Schannel MsQuic refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12 with QUIC_STATUS_NOT_SUPPORTED, and PKCS12 is the only credential type a provider has on both platforms, so a Windows provider could not open a listener against the runtime this repo was shipping. The same bridge binary passes all twenty smoke checks and the v1 gate against the OpenSSL build, which is what Soul verified and what this now downloads. Two digests, not one. The zip is pinned by FEE9A664 and 19,179,817 bytes; the msquic.dll inside it by C17C6581 and 4,181,856 bytes. The earlier report and the cut map's first record put the zip's digest on the DLL, which is a pin that checks the archive and then ships a file nobody looked at. Sizes ride along with both, because a digest that fails tells you less than a digest and a length that fail together. The CRT is linked statically. A Node or Unity host without the Visual C++ redistributable cannot load a DLL that imports vcruntime140.dll, and Windows reports that as "the specified module could not be found" while naming the module that was found. Confirmed on the rebuild: msquic.dll, CRYPT32, ntdll, KERNEL32, and no CRT import at all. The DLL goes from 83,456 to 302,080 bytes, which is the price of the host not needing anything installed. The Unity plugin's committed msquic.dll is still the Schannel one and this does not touch it; the next build-unity-package.ps1 run is what replaces it, and that is not in this batch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:04:27 +02:00
message(FATAL_ERROR "MSQUIC_ROOT must point at the extracted Microsoft.Native.Quic.MsQuic.OpenSSL package build/native directory")
Make the MsQuic bridge a two-role event queue instead of a Unity client The bridge was one platform, one role and one host: Windows by construction (`windows.h`, `__declspec`, `CryptHashCertificate2`), client-only, and shaped around the managed connector that polls it every 2 ms. Nothing about MsQuic required that; the shape came from having exactly one caller. It now carries a C ABI v2 of twelve exports over a single per-runtime event queue: open a runtime, open listeners and connections on it, open streams and send frames, and take typed events out of one blocking wait. The event struct is 64 bytes described by layout rather than by a shared header, so a host declares it in its own FFI and no header travels with the ABI. `next_event` keeps the v1 two-phase convention — return 2, leave the event at the head, report the size the caller needs — because that is the one part of v1 that was already host-agnostic. The rule that shapes everything else is that nothing calls out. MsQuic's worker threads only push onto the queue and wake that wait, so no host runtime is entered from a thread it does not own, and the bridge needs to know nothing about what is on the other side. That is what makes one binary serve both a Node process through FFI and a Unity player through P/Invoke. Trust moved out. A v2 client connection defers validation, emits the DER as event 3 and answers MsQuic only when the host calls `connection_certificate_complete`; if the host never answers, MsQuic's handshake timeout closes the connection and event 4 says so. The C code no longer has an opinion about certificates — except in the v1 shim, where it must. The five v1 exports are unchanged in signature and behaviour and are now `#ifdef _WIN32`, matching `CanConnect`'s own Windows gate. A v1 client is a private v2 runtime holding one connection, and the pin comparison is the single thing it still decides in C, because that is the contract the shipped Unity package was built against. `windows.h`, `wincrypt.h` and `CryptHashCertificate2` appear nowhere outside that block. Two things the rewrite had to get right that the old shape never exercised. The v1 pin must be attached before `ConnectionStart`, not after it returns: the handshake runs on an MsQuic worker thread and reaches the certificate event immediately on loopback, so a pin applied afterwards loses the race and the connection silently takes the host path nobody is listening on. And teardown must close the connection handles itself — `RegistrationClose` blocks until the app has closed every child handle, and a silent shutdown delivers no shutdown events at all, so leaning on the callbacks to do it deadlocks the caller forever. Both were found by the tests hanging, and both now have a mutation that reproduces them. Verified: the four v1 tests in tests/GameCult.Mesh.Quic.Native.Tests pass unchanged (3 passed, 1 skipped for want of an external endpoint), including the wrong-pin refusal and the departed-client broadcast test. A 15-check FFI smoke drives the whole v2 path — listener, deferred certificate, both sides connected, latest-only stream, one frame with FIN, identical bytes out — and passes on Windows x64 and on Linux x64. Ten source mutations, ten killed, two green no-op controls. One open fork, reported separately and not decided here: the pinned Windows MsQuic is the Schannel build, which refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12 with QUIC_STATUS_NOT_SUPPORTED, so `listener_open` cannot open a provider against it. The map assumed PKCS12 was "one credential type for Schannel and OpenSSL"; the header declares it but the Schannel provider does not implement it. The identical bridge binary passes the full provider smoke against OpenSSL MsQuic 2.5.9, so this is a question about which flavour Windows pins, not about the bridge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 21:03:01 +02:00
endif()
Pin the Windows MsQuic to the OpenSSL build, and both of its digests Q9 ruled A. The Schannel MsQuic refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12 with QUIC_STATUS_NOT_SUPPORTED, and PKCS12 is the only credential type a provider has on both platforms, so a Windows provider could not open a listener against the runtime this repo was shipping. The same bridge binary passes all twenty smoke checks and the v1 gate against the OpenSSL build, which is what Soul verified and what this now downloads. Two digests, not one. The zip is pinned by FEE9A664 and 19,179,817 bytes; the msquic.dll inside it by C17C6581 and 4,181,856 bytes. The earlier report and the cut map's first record put the zip's digest on the DLL, which is a pin that checks the archive and then ships a file nobody looked at. Sizes ride along with both, because a digest that fails tells you less than a digest and a length that fail together. The CRT is linked statically. A Node or Unity host without the Visual C++ redistributable cannot load a DLL that imports vcruntime140.dll, and Windows reports that as "the specified module could not be found" while naming the module that was found. Confirmed on the rebuild: msquic.dll, CRYPT32, ntdll, KERNEL32, and no CRT import at all. The DLL goes from 83,456 to 302,080 bytes, which is the price of the host not needing anything installed. The Unity plugin's committed msquic.dll is still the Schannel one and this does not touch it; the next build-unity-package.ps1 run is what replaces it, and that is not in this batch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 22:04:27 +02:00
# Static CRT. A Node or Unity host without the Visual C++ redistributable
# installed cannot load a DLL that imports vcruntime140.dll, and the failure
# arrives as "the specified module could not be found" with nothing naming the
# missing runtime. The bridge carries its own instead.
set_property(TARGET gamecult_mesh_quic_native PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
Make the MsQuic bridge a two-role event queue instead of a Unity client The bridge was one platform, one role and one host: Windows by construction (`windows.h`, `__declspec`, `CryptHashCertificate2`), client-only, and shaped around the managed connector that polls it every 2 ms. Nothing about MsQuic required that; the shape came from having exactly one caller. It now carries a C ABI v2 of twelve exports over a single per-runtime event queue: open a runtime, open listeners and connections on it, open streams and send frames, and take typed events out of one blocking wait. The event struct is 64 bytes described by layout rather than by a shared header, so a host declares it in its own FFI and no header travels with the ABI. `next_event` keeps the v1 two-phase convention — return 2, leave the event at the head, report the size the caller needs — because that is the one part of v1 that was already host-agnostic. The rule that shapes everything else is that nothing calls out. MsQuic's worker threads only push onto the queue and wake that wait, so no host runtime is entered from a thread it does not own, and the bridge needs to know nothing about what is on the other side. That is what makes one binary serve both a Node process through FFI and a Unity player through P/Invoke. Trust moved out. A v2 client connection defers validation, emits the DER as event 3 and answers MsQuic only when the host calls `connection_certificate_complete`; if the host never answers, MsQuic's handshake timeout closes the connection and event 4 says so. The C code no longer has an opinion about certificates — except in the v1 shim, where it must. The five v1 exports are unchanged in signature and behaviour and are now `#ifdef _WIN32`, matching `CanConnect`'s own Windows gate. A v1 client is a private v2 runtime holding one connection, and the pin comparison is the single thing it still decides in C, because that is the contract the shipped Unity package was built against. `windows.h`, `wincrypt.h` and `CryptHashCertificate2` appear nowhere outside that block. Two things the rewrite had to get right that the old shape never exercised. The v1 pin must be attached before `ConnectionStart`, not after it returns: the handshake runs on an MsQuic worker thread and reaches the certificate event immediately on loopback, so a pin applied afterwards loses the race and the connection silently takes the host path nobody is listening on. And teardown must close the connection handles itself — `RegistrationClose` blocks until the app has closed every child handle, and a silent shutdown delivers no shutdown events at all, so leaning on the callbacks to do it deadlocks the caller forever. Both were found by the tests hanging, and both now have a mutation that reproduces them. Verified: the four v1 tests in tests/GameCult.Mesh.Quic.Native.Tests pass unchanged (3 passed, 1 skipped for want of an external endpoint), including the wrong-pin refusal and the departed-client broadcast test. A 15-check FFI smoke drives the whole v2 path — listener, deferred certificate, both sides connected, latest-only stream, one frame with FIN, identical bytes out — and passes on Windows x64 and on Linux x64. Ten source mutations, ten killed, two green no-op controls. One open fork, reported separately and not decided here: the pinned Windows MsQuic is the Schannel build, which refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12 with QUIC_STATUS_NOT_SUPPORTED, so `listener_open` cannot open a provider against it. The map assumed PKCS12 was "one credential type for Schannel and OpenSSL"; the header declares it but the Schannel provider does not implement it. The identical bridge binary passes the full provider smoke against OpenSSL MsQuic 2.5.9, so this is a question about which flavour Windows pins, not about the bridge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 21:03:01 +02:00
target_compile_definitions(gamecult_mesh_quic_native PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
target_include_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_ROOT}/include")
target_link_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_ROOT}/lib/x64")
# crypt32 and bcrypt belong to the v1 shim alone: CryptHashCertificate2
# compares the Unity pin. Nothing in the v2 path needs them. ntdll carries
# RtlIpv4/6StringToAddressEx, which msquic_winuser.h's inline QuicAddrFromString
# calls to parse the listener host; the Linux header parses it in-tree instead.
target_link_libraries(gamecult_mesh_quic_native PRIVATE msquic crypt32 bcrypt ntdll)
# The DLL is committed beside its source, so a rebuild must reproduce it: /Brepro drops the link timestamps.
target_compile_options(gamecult_mesh_quic_native PRIVATE /Brepro)
target_link_options(gamecult_mesh_quic_native PRIVATE /Brepro)
else()
if(NOT DEFINED MSQUIC_INCLUDE_DIR OR NOT DEFINED MSQUIC_LIB_DIR)
message(FATAL_ERROR "MSQUIC_INCLUDE_DIR and MSQUIC_LIB_DIR must point at the msquic v2.5.9 headers and the extracted libmsquic library directory")
endif()
target_include_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_INCLUDE_DIR}")
target_link_directories(gamecult_mesh_quic_native PRIVATE "${MSQUIC_LIB_DIR}")
target_link_libraries(gamecult_mesh_quic_native PRIVATE msquic pthread)
# Only the CULTMESH_API exports leave the .so; everything else stays internal.
target_compile_options(gamecult_mesh_quic_native PRIVATE -fvisibility=hidden -fvisibility-inlines-hidden)
# libmsquic.so.2 ships beside the bridge rather than being installed
# system-wide, so the loader is pointed at the bridge's own directory.
set_target_properties(gamecult_mesh_quic_native PROPERTIES
BUILD_WITH_INSTALL_RPATH TRUE
INSTALL_RPATH "$ORIGIN")
endif()
set_target_properties(gamecult_mesh_quic_native PROPERTIES
OUTPUT_NAME "gamecult_mesh_quic_native"
Make the MsQuic bridge a two-role event queue instead of a Unity client The bridge was one platform, one role and one host: Windows by construction (`windows.h`, `__declspec`, `CryptHashCertificate2`), client-only, and shaped around the managed connector that polls it every 2 ms. Nothing about MsQuic required that; the shape came from having exactly one caller. It now carries a C ABI v2 of twelve exports over a single per-runtime event queue: open a runtime, open listeners and connections on it, open streams and send frames, and take typed events out of one blocking wait. The event struct is 64 bytes described by layout rather than by a shared header, so a host declares it in its own FFI and no header travels with the ABI. `next_event` keeps the v1 two-phase convention — return 2, leave the event at the head, report the size the caller needs — because that is the one part of v1 that was already host-agnostic. The rule that shapes everything else is that nothing calls out. MsQuic's worker threads only push onto the queue and wake that wait, so no host runtime is entered from a thread it does not own, and the bridge needs to know nothing about what is on the other side. That is what makes one binary serve both a Node process through FFI and a Unity player through P/Invoke. Trust moved out. A v2 client connection defers validation, emits the DER as event 3 and answers MsQuic only when the host calls `connection_certificate_complete`; if the host never answers, MsQuic's handshake timeout closes the connection and event 4 says so. The C code no longer has an opinion about certificates — except in the v1 shim, where it must. The five v1 exports are unchanged in signature and behaviour and are now `#ifdef _WIN32`, matching `CanConnect`'s own Windows gate. A v1 client is a private v2 runtime holding one connection, and the pin comparison is the single thing it still decides in C, because that is the contract the shipped Unity package was built against. `windows.h`, `wincrypt.h` and `CryptHashCertificate2` appear nowhere outside that block. Two things the rewrite had to get right that the old shape never exercised. The v1 pin must be attached before `ConnectionStart`, not after it returns: the handshake runs on an MsQuic worker thread and reaches the certificate event immediately on loopback, so a pin applied afterwards loses the race and the connection silently takes the host path nobody is listening on. And teardown must close the connection handles itself — `RegistrationClose` blocks until the app has closed every child handle, and a silent shutdown delivers no shutdown events at all, so leaning on the callbacks to do it deadlocks the caller forever. Both were found by the tests hanging, and both now have a mutation that reproduces them. Verified: the four v1 tests in tests/GameCult.Mesh.Quic.Native.Tests pass unchanged (3 passed, 1 skipped for want of an external endpoint), including the wrong-pin refusal and the departed-client broadcast test. A 15-check FFI smoke drives the whole v2 path — listener, deferred certificate, both sides connected, latest-only stream, one frame with FIN, identical bytes out — and passes on Windows x64 and on Linux x64. Ten source mutations, ten killed, two green no-op controls. One open fork, reported separately and not decided here: the pinned Windows MsQuic is the Schannel build, which refuses QUIC_CREDENTIAL_TYPE_CERTIFICATE_PKCS12 with QUIC_STATUS_NOT_SUPPORTED, so `listener_open` cannot open a provider against it. The map assumed PKCS12 was "one credential type for Schannel and OpenSSL"; the header declares it but the Schannel provider does not implement it. The identical bridge binary passes the full provider smoke against OpenSSL MsQuic 2.5.9, so this is a question about which flavour Windows pins, not about the bridge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 21:03:01 +02:00
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
# The runtime-lifetime scenarios. Off by default: the shipped artifact is the
# library alone, and a delivery build has no business producing an executable.
# CULTMESH_QUIC_DEBUG_ASSERTS goes with them, never into a shipped binary — it
# makes the bridge state the quiesce invariant instead of leaving it to whatever
# a sanitizer happens to catch.
option(CULTMESH_QUIC_BUILD_TESTS "Build the runtime-lifetime scenario runner" OFF)
option(CULTMESH_QUIC_DEBUG_ASSERTS "Abort the bridge on a violated internal invariant" OFF)
if(CULTMESH_QUIC_DEBUG_ASSERTS)
target_compile_definitions(gamecult_mesh_quic_native PRIVATE CULTMESH_QUIC_DEBUG_ASSERTS)
endif()
if(CULTMESH_QUIC_BUILD_TESTS)
add_executable(cultmesh_quic_native_tests tests/cultmesh_quic_native_tests.cpp)
target_compile_features(cultmesh_quic_native_tests PRIVATE cxx_std_20)
# The header only. The scenarios drive the bridge as a host does, so they get
# no more of it than a host has.
target_link_libraries(cultmesh_quic_native_tests PRIVATE gamecult_mesh_quic_native)
# The scenarios see the development seam (section 7 of the header) exactly when
# the library has one, so `holdclose` compiles out of the same builds the
# bridge's own assertions compile out of.
if(CULTMESH_QUIC_DEBUG_ASSERTS)
target_compile_definitions(cultmesh_quic_native_tests PRIVATE CULTMESH_QUIC_DEBUG_ASSERTS)
endif()
set_target_properties(cultmesh_quic_native_tests PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
if(WIN32)
# gamecult_mesh_quic_native.dll imports msquic.dll, and the scenario
# runner loads it from its own directory (or PATH) at process start, not
# from MSQUIC_ROOT. build-quic-native.ps1's packaged output gets a copy
# from its own recipe; a test build has no packaging step, so without this
# the loader falls through to whatever msquic.dll happens to already be on
# PATH — silently running the scenarios against an unpinned, wrong-version
# MsQuic instead of failing to start. CMake already knows MSQUIC_ROOT, so
# it places the pinned DLL itself instead of leaving the reader to guess
# why a scenario passed against the wrong binary.
add_custom_command(TARGET cultmesh_quic_native_tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${MSQUIC_ROOT}/bin/x64/msquic.dll"
"$<TARGET_FILE_DIR:cultmesh_quic_native_tests>/msquic.dll")
else()
target_link_libraries(cultmesh_quic_native_tests PRIVATE pthread)
# The linker resolves the bridge's own NEEDED libmsquic.so.2 while linking
# this executable, and the runtime copy only lands beside the binaries after
# the build; point it at the extracted package instead.
target_link_options(cultmesh_quic_native_tests PRIVATE "-Wl,-rpath-link,${MSQUIC_LIB_DIR}")
set_target_properties(cultmesh_quic_native_tests PROPERTIES
BUILD_WITH_INSTALL_RPATH TRUE
INSTALL_RPATH "$ORIGIN")
# Both binaries carry INSTALL_RPATH "$ORIGIN" and expect libmsquic.so.2
# beside them at load time. build-quic-native.sh's packaged output gets
# that copy from its own recipe; a test build has no packaging step, so
# the runner would otherwise die at once with "cannot open shared object
# file". CMake already knows where MSQUIC_LIB_DIR is, so it places the
# file itself instead of leaving the reader to run an extra cp by hand.
add_custom_command(TARGET cultmesh_quic_native_tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${MSQUIC_LIB_DIR}/libmsquic.so.2"
"$<TARGET_FILE_DIR:cultmesh_quic_native_tests>/libmsquic.so.2")
endif()
endif()