/******************************************************************************** * Copyright (c) 2025-2026 ZF Friedrichshafen AG * * This program and the accompanying materials are made available under the * terms of the Apache License Version 2.0 which is available at * https://www.apache.org/licenses/LICENSE-2.0 * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ #if defined(_WIN32) #include "gtest/gtest.h" #include #include #include "../../../sdv_services/uds_win_sockets/channel_mgnt.h" #include "../../../sdv_services/uds_win_sockets/connection.h" #include #include #include #include #include #include #include #include #include #include #include // Helper namespace namespace test_utils { /** * @brief Ensure the directory for the given full path exists */ inline void EnsureParentDir(const std::string& fullPath) { auto pos = fullPath.find_last_of("\\/"); if (pos == std::string::npos) return; std::string dir = fullPath.substr(0, pos); CreateDirectoryA(dir.c_str(), nullptr); } /** * @brief Expand environment variables such as %LOCALAPPDATA% */ inline std::string Expand(const std::string& in) { if (in.find('%') == std::string::npos) return in; char buf[4096] = {}; DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, sizeof(buf)); return (n > 0 && n < sizeof(buf)) ? std::string(buf) : in; } /** * @brief Build a short Win32 path for UDS sockets */ inline std::string MakeShortUdsPath(const char* name) { std::string base = R"(%LOCALAPPDATA%\sdv\)"; base = Expand(base); EnsureParentDir(base); return base + name; } /** * @brief Generate a random hex suffix for unique paths */ inline std::string RandomHex() { std::mt19937_64 rng{ std::random_device{}() }; std::uniform_int_distribution dist; std::ostringstream oss; oss << std::hex << dist(rng); return oss.str(); } /** * @brief Unique absolute path (keeps tests independent) */ inline std::string UniqueUds(const char* prefix) { return MakeShortUdsPath((std::string(prefix) + "_" + RandomHex() + ".sock").c_str()); } /** * @brief Wait until a server connection worker has reached "armed" state */ inline void SpinUntilServerArmed(sdv::ipc::IConnect* server, uint32_t maxWaitMs = 300) { using namespace std::chrono; auto deadline = steady_clock::now() + milliseconds(maxWaitMs); while (server->GetConnectState() == sdv::ipc::EConnectState::uninitialized && steady_clock::now() < deadline) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } /** * @brief Small sleep helper for teardown time */ inline void SleepTiny(uint32_t ms = 20) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } } // namespace test_utils // Unified test receiver (state + data) class CTestReceiver : public sdv::IInterfaceAccess, public sdv::ipc::IConnectEventCallback, public sdv::ipc::IDataReceiveCallback { public: BEGIN_SDV_INTERFACE_MAP() SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback) SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback) END_SDV_INTERFACE_MAP() void SetConnectState(sdv::ipc::EConnectState s) override { { std::lock_guard lk(m_mtx); m_state = s; } m_cv.notify_all(); } void ReceiveData(sdv::sequence>& seq) override { { std::lock_guard lk(m_mtx); m_data = seq; m_hasData = true; } m_cv.notify_all(); } bool WaitForState(sdv::ipc::EConnectState expected, uint32_t ms = 2000) { std::unique_lock lk(m_mtx); return m_cv.wait_for(lk, std::chrono::milliseconds(ms), [&]{ return m_state == expected; }); } bool WaitForData(uint32_t ms = 2000) { std::unique_lock lk(m_mtx); return m_cv.wait_for(lk, std::chrono::milliseconds(ms), [&]{ return m_hasData; }); } sdv::sequence> Data() { std::lock_guard lk(m_mtx); return m_data; } private: mutable std::mutex m_mtx; std::condition_variable m_cv; sdv::ipc::EConnectState m_state{ sdv::ipc::EConnectState::uninitialized }; sdv::sequence> m_data; bool m_hasData{false}; }; // Helper for creating server + client on a given UDS connect-string struct ServerClient { sdv::TObjectPtr serverObj; sdv::ipc::IConnect* server = nullptr; sdv::TObjectPtr clientObj; sdv::ipc::IConnect* client = nullptr; }; static ServerClient CreatePair(CSocketsChannelMgnt& mgr, const std::string& cs) { ServerClient out; // Server out.serverObj = mgr.Access(cs); out.server = out.serverObj ? out.serverObj.GetInterface() : nullptr; // Client out.clientObj = mgr.Access(cs); out.client = out.clientObj ? out.clientObj.GetInterface() : nullptr; return out; } // TESTS START HERE using namespace test_utils; // Instantiate manager TEST(WindowsAFUnixIPC, Instantiate) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); CSocketsChannelMgnt mgr; EXPECT_NO_THROW(mgr.Initialize("")); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); EXPECT_NO_THROW(mgr.Shutdown()); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending); app.Shutdown(); } // Basic connect/disconnect (server + client) TEST(WindowsAFUnixIPC, BasicConnectDisconnect) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string path = UniqueUds("basic"); const std::string cs = std::string("proto=uds;path=") + path + ";"; // Create endpoint auto ep = mgr.CreateEndpoint(cs); ASSERT_FALSE(ep.ssConnectString.empty()); // Create server + client objects auto pair = CreatePair(mgr, ep.ssConnectString); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); CTestReceiver sr, cr; // Server side connect pair.server->AsyncConnect(&sr); SpinUntilServerArmed(pair.server); // Client side connect pair.client->AsyncConnect(&cr); EXPECT_TRUE(pair.server->WaitForConnection(5000)); EXPECT_TRUE(pair.client->WaitForConnection(5000)); pair.client->Disconnect(); pair.server->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // Simple data path: client -> server sends "hello" TEST(WindowsAFUnixIPC, DataPath_SimpleHello) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + UniqueUds("hello") + ";"; auto ep = mgr.CreateEndpoint(cs); auto pair = CreatePair(mgr, ep.ssConnectString); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); CTestReceiver sr, cr; pair.server->AsyncConnect(&sr); SpinUntilServerArmed(pair.server); pair.client->AsyncConnect(&cr); ASSERT_TRUE(pair.server->WaitForConnection(5000)); ASSERT_TRUE(pair.client->WaitForConnection(5000)); // Build hello payload sdv::pointer p; p.resize(5); std::memcpy(p.get(), "hello", 5); sdv::sequence> seq; seq.push_back(p); auto* sender = dynamic_cast(pair.client); ASSERT_NE(sender, nullptr); EXPECT_TRUE(sender->SendData(seq)); EXPECT_TRUE(sr.WaitForData(3000)); auto recv = sr.Data(); ASSERT_EQ(recv.size(), 1u); ASSERT_EQ(recv[0].size(), 5u); EXPECT_EQ(std::memcmp(recv[0].get(), "hello", 5), 0); pair.client->Disconnect(); pair.server->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // Server disconnect propagates to client TEST(WindowsAFUnixIPC, ServerDisconnectPropagates) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + UniqueUds("disc") + ";"; auto ep = mgr.CreateEndpoint(cs); auto pair = CreatePair(mgr, ep.ssConnectString); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); CTestReceiver sr, cr; pair.server->AsyncConnect(&sr); SpinUntilServerArmed(pair.server); pair.client->AsyncConnect(&cr); ASSERT_TRUE(pair.server->WaitForConnection(5000)); ASSERT_TRUE(pair.client->WaitForConnection(5000)); pair.server->Disconnect(); EXPECT_TRUE(cr.WaitForState(sdv::ipc::EConnectState::disconnected, 3000)); pair.client->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // DataPath – multi-chunk payload TEST(WindowsAFUnixIPC, DataPath_MultiChunk) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + UniqueUds("mc") + ";"; auto ep = mgr.CreateEndpoint(cs); auto pair = CreatePair(mgr, ep.ssConnectString); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); CTestReceiver sr, cr; pair.server->AsyncConnect(&sr); SpinUntilServerArmed(pair.server); pair.client->AsyncConnect(&cr); ASSERT_TRUE(pair.server->WaitForConnection(5000)); ASSERT_TRUE(pair.client->WaitForConnection(5000)); // Build multichunk sdv::pointer p1, p2; p1.resize(3); std::memcpy(p1.get(), "sdv", 3); p2.resize(9); std::memcpy(p2.get(), "framework", 9); sdv::sequence> seq; seq.push_back(p1); seq.push_back(p2); auto* sender = dynamic_cast(pair.client); ASSERT_NE(sender, nullptr); EXPECT_TRUE(sender->SendData(seq)); ASSERT_TRUE(sr.WaitForData(3000)); auto recv = sr.Data(); ASSERT_EQ(recv.size(), 2u); EXPECT_EQ(std::memcmp(recv[0].get(), "sdv", 3), 0); EXPECT_EQ(std::memcmp(recv[1].get(), "framework", 9), 0); pair.client->Disconnect(); pair.server->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // Large payload fragmentation + reassembly TEST(WindowsAFUnixIPC, DataPath_LargePayloadFragmentation) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + UniqueUds("big") + ";"; auto ep = mgr.CreateEndpoint(cs); auto pair = CreatePair(mgr, ep.ssConnectString); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); CTestReceiver sr, cr; pair.server->AsyncConnect(&sr); SpinUntilServerArmed(pair.server); pair.client->AsyncConnect(&cr); ASSERT_TRUE(pair.server->WaitForConnection(5000)); ASSERT_TRUE(pair.client->WaitForConnection(5000)); const size_t N = 256 * 1024; sdv::pointer payload; payload.resize(N); for (size_t i = 0; i < N; ++i) payload.get()[i] = uint8_t(i & 0xFF); sdv::sequence> seq; seq.push_back(payload); auto* sender = dynamic_cast(pair.client); ASSERT_NE(sender, nullptr); EXPECT_TRUE(sender->SendData(seq)); ASSERT_TRUE(sr.WaitForData(5000)); auto recv = sr.Data(); ASSERT_EQ(recv.size(), 1u); ASSERT_EQ(recv[0].size(), N); EXPECT_EQ(std::memcmp(recv[0].get(), payload.get(), N), 0); pair.client->Disconnect(); pair.server->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // Zero-length chunks preserved TEST(WindowsAFUnixIPC, DataPath_ZeroLengthChunks) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + UniqueUds("zlen") + ";"; auto ep = mgr.CreateEndpoint(cs); auto pair = CreatePair(mgr, ep.ssConnectString); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); CTestReceiver sr, cr; pair.server->AsyncConnect(&sr); SpinUntilServerArmed(pair.server); pair.client->AsyncConnect(&cr); ASSERT_TRUE(pair.server->WaitForConnection(5000)); ASSERT_TRUE(pair.client->WaitForConnection(5000)); sdv::pointer p0a, p5, p0b, p8; p0a.resize(0); p5.resize(5); std::memcpy(p5.get(), "world",5); p0b.resize(0); p8.resize(8); std::memcpy(p8.get(), "fragment",8); sdv::sequence> seq; seq.push_back(p0a); seq.push_back(p5); seq.push_back(p0b); seq.push_back(p8); auto* sender = dynamic_cast(pair.client); ASSERT_NE(sender, nullptr); EXPECT_TRUE(sender->SendData(seq)); ASSERT_TRUE(sr.WaitForData(3000)); auto recv = sr.Data(); ASSERT_EQ(recv.size(), 4u); EXPECT_EQ(recv[0].size(), 0u); EXPECT_EQ(recv[1].size(), 5u); EXPECT_EQ(recv[2].size(), 0u); EXPECT_EQ(recv[3].size(), 8u); pair.client->Disconnect(); pair.server->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // Operation mode transitions TEST(WindowsAFUnixIPC, OperationModeTransitions) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); CSocketsChannelMgnt mgr; mgr.Initialize(""); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized); mgr.SetOperationMode(sdv::EOperationMode::configuring); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::configuring); mgr.SetOperationMode(sdv::EOperationMode::running); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::running); mgr.Shutdown(); EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending); app.Shutdown(); } // Reconnect using the same UDS path (two consecutive sessions) TEST(WindowsAFUnixIPC, ReconnectAfterDisconnect_SamePath) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string udsPath = MakeShortUdsPath(("vapi_win_reconn_" + RandomHex() + ".sock").c_str()); const std::string cs = std::string("proto=uds;path=") + udsPath + ";"; // ----- Session 1 ----- { auto ep = mgr.CreateEndpoint(cs); auto pair = CreatePair(mgr, ep.ssConnectString); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); CTestReceiver sr, cr; pair.server->AsyncConnect(&sr); SpinUntilServerArmed(pair.server); std::atomic clientRes{0}; std::thread ct([&]{ if (!pair.clientObj) { clientRes = 1; return; } auto* c = pair.client; if (!c) { clientRes = 2; return; } c->AsyncConnect(&cr); if (!c->WaitForConnection(5000)) { clientRes = 3; return; } c->Disconnect(); clientRes = 0; }); EXPECT_TRUE(pair.server->WaitForConnection(5000)); ct.join(); EXPECT_EQ(clientRes.load(), 0); pair.server->Disconnect(); } SleepTiny(20); // ----- Session 2 ----- { auto ep = mgr.CreateEndpoint(cs); auto pair = CreatePair(mgr, ep.ssConnectString); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); CTestReceiver sr, cr; pair.server->AsyncConnect(&sr); SpinUntilServerArmed(pair.server); std::atomic clientRes{0}; std::thread ct([&]{ if (!pair.clientObj) { clientRes = 1; return; } auto* c = pair.client; if (!c) { clientRes = 2; return; } c->AsyncConnect(&cr); if (!c->WaitForConnection(5000)) { clientRes = 3; return; } c->Disconnect(); clientRes = 0; }); EXPECT_TRUE(pair.server->WaitForConnection(5000)); ct.join(); EXPECT_EQ(clientRes.load(), 0); pair.server->Disconnect(); } mgr.Shutdown(); app.Shutdown(); } // WaitForConnection(INFINITE) with a delayed client TEST(WindowsAFUnixIPC, WaitForConnection_InfiniteWait_SlowClient) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + MakeShortUdsPath(("vapi_win_slow_" + RandomHex() + ".sock").c_str()) + ";"; auto ep = mgr.CreateEndpoint(cs); sdv::TObjectPtr sObj = mgr.Access(ep.ssConnectString); auto* server = sObj.GetInterface(); ASSERT_NE(server, nullptr); CTestReceiver sr; server->AsyncConnect(&sr); SpinUntilServerArmed(server); std::thread delayedClient([&]{ std::this_thread::sleep_for(std::chrono::milliseconds(200)); sdv::TObjectPtr cObj = mgr.Access(ep.ssConnectString); auto* client = cObj.GetInterface(); CTestReceiver cr; client->AsyncConnect(&cr); client->WaitForConnection(5000); client->Disconnect(); }); EXPECT_TRUE(server->WaitForConnection(6000)); delayedClient.join(); server->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // WaitForConnection(0) before and after establishing the connection TEST(WindowsAFUnixIPC, WaitForConnection_ZeroTimeout_BeforeAndAfter) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + MakeShortUdsPath(("vapi_win_zero_" + RandomHex() + ".sock").c_str()) + ";"; auto ep = mgr.CreateEndpoint(cs); sdv::TObjectPtr sObj = mgr.Access(ep.ssConnectString); auto* server = sObj.GetInterface(); ASSERT_NE(server, nullptr); CTestReceiver sr; server->AsyncConnect(&sr); SpinUntilServerArmed(server); EXPECT_FALSE(server->WaitForConnection(0)); sdv::TObjectPtr cObj = mgr.Access(ep.ssConnectString); auto* client = cObj.GetInterface(); CTestReceiver cr; client->AsyncConnect(&cr); EXPECT_TRUE(server->WaitForConnection(5000)); EXPECT_TRUE(client->WaitForConnection(5000)); client->Disconnect(); server->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // Client timeout when NO server exists on that path TEST(WindowsAFUnixIPC, ClientTimeout_NoServer) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup(R"toml([LogHandler] ViewFilter = "Fatal")toml")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string raw = MakeShortUdsPath(("vapi_win_nosrv_" + RandomHex() + ".sock").c_str()); const std::string cs = std::string("proto=uds;path=") + raw + ";"; sdv::TObjectPtr cObj = mgr.Access(cs); if (!cObj) { SUCCEED() << "Access(proto=uds;path=..., no-server) may return nullptr immediately on Windows."; mgr.Shutdown(); app.Shutdown(); return; } auto* client = cObj.GetInterface(); ASSERT_NE(client, nullptr); CTestReceiver cr; client->AsyncConnect(&cr); EXPECT_FALSE(client->WaitForConnection(1500)); std::this_thread::sleep_for(std::chrono::milliseconds(800)); EXPECT_EQ(client->GetConnectState(), sdv::ipc::EConnectState::connection_error); client->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // Server closes the connection during a large transfer → client sees disconnected TEST(WindowsAFUnixIPC, PeerCloseMidTransfer_ClientDetectsDisconnect) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + MakeShortUdsPath(("vapi_win_midclose_" + RandomHex() + ".sock").c_str()) + ";"; auto ep = mgr.CreateEndpoint(cs); auto pair = CreatePair(mgr, ep.ssConnectString); ASSERT_NE(pair.server, nullptr); ASSERT_NE(pair.client, nullptr); CTestReceiver sr, cr; pair.server->AsyncConnect(&sr); SpinUntilServerArmed(pair.server); pair.client->AsyncConnect(&cr); ASSERT_TRUE(pair.server->WaitForConnection(5000)); ASSERT_TRUE(pair.client->WaitForConnection(5000)); sdv::pointer buf; buf.resize(512 * 1024); std::memset(buf.get(), 0xAA, buf.size()); sdv::sequence> seq; seq.push_back(buf); auto* sender = dynamic_cast(pair.client); ASSERT_NE(sender, nullptr); std::atomic sendOk{true}; std::thread t([&]{ sendOk.store(sender->SendData(seq)); }); std::this_thread::sleep_for(std::chrono::milliseconds(50)); pair.server->Disconnect(); t.join(); EXPECT_TRUE(cr.WaitForState(sdv::ipc::EConnectState::disconnected, 3000)); pair.client->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // Client cancels connect attempt when no server exists → must clean up promptly TEST(WindowsAFUnixIPC, ClientCancelConnect_NoServer_Cleanup) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup(R"toml([LogHandler] ViewFilter = "Fatal")toml")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string raw = MakeShortUdsPath(("vapi_win_cancel_" + RandomHex() + ".sock").c_str()); const std::string cs = std::string("proto=uds;path=") + raw + ";"; sdv::TObjectPtr cObj = mgr.Access(cs); if (!cObj) { SUCCEED() << "Immediate nullptr for no-server case is valid Windows behavior."; mgr.Shutdown(); app.Shutdown(); return; } auto* client = cObj.GetInterface(); ASSERT_NE(client, nullptr); CTestReceiver cr; client->AsyncConnect(&cr); std::this_thread::sleep_for(std::chrono::milliseconds(150)); client->Disconnect(); EXPECT_EQ(client->GetConnectState(), sdv::ipc::EConnectState::disconnected); mgr.Shutdown(); app.Shutdown(); } // Server starts and immediately disconnects (no client) TEST(WindowsAFUnixIPC, ServerStartThenImmediateDisconnect_NoClient) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup(R"toml([LogHandler] ViewFilter = "Fatal")toml")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + MakeShortUdsPath(("vapi_win_srvonly_" + RandomHex() + ".sock").c_str()) + ";"; auto ep = mgr.CreateEndpoint(cs); sdv::TObjectPtr sObj = mgr.Access(ep.ssConnectString); auto* server = sObj.GetInterface(); ASSERT_NE(server, nullptr); CTestReceiver sr; server->AsyncConnect(&sr); SpinUntilServerArmed(server); std::this_thread::sleep_for(std::chrono::milliseconds(50)); server->Disconnect(); EXPECT_EQ(server->GetConnectState(), sdv::ipc::EConnectState::disconnected); mgr.Shutdown(); app.Shutdown(); } // UnregisterStateEventCallback: ensure single-listener semantics TEST(WindowsAFUnixIPC, UnregisterStateEventCallback_SingleListenerSemantics) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); const std::string cs = std::string("proto=uds;path=") + MakeShortUdsPath(("vapi_win_cb_" + RandomHex() + ".sock").c_str()) + ";"; auto ep = mgr.CreateEndpoint(cs); sdv::TObjectPtr sObj = mgr.Access(ep.ssConnectString); auto* server = sObj.GetInterface(); ASSERT_NE(server, nullptr); sdv::TObjectPtr cObj = mgr.Access(ep.ssConnectString); auto* client = cObj.GetInterface(); ASSERT_NE(client, nullptr); CTestReceiver regListener; const uint64_t cookie = server->RegisterStateEventCallback(®Listener); ASSERT_NE(cookie, 0u); CTestReceiver mainRecv; server->AsyncConnect(&mainRecv); client->AsyncConnect(&mainRecv); SpinUntilServerArmed(server); EXPECT_TRUE(server->WaitForConnection(5000)); EXPECT_TRUE(client->WaitForConnection(5000)); EXPECT_TRUE(mainRecv.WaitForState(sdv::ipc::EConnectState::connected, 1000)); EXPECT_FALSE(regListener.WaitForState(sdv::ipc::EConnectState::connected, 300)) << "The registry listener should NOT receive events while main receiver is active."; server->UnregisterStateEventCallback(cookie); client->Disconnect(); EXPECT_TRUE(mainRecv.WaitForState(sdv::ipc::EConnectState::disconnected, 1500)); EXPECT_FALSE(regListener.WaitForState(sdv::ipc::EConnectState::disconnected, 300)); server->Disconnect(); mgr.Shutdown(); app.Shutdown(); } // CreateEndpoint with very long path → must be normalized to basename TEST(WindowsAFUnixIPC, CreateEndpoint_LongInputPath_Normalized) { sdv::app::CAppControl app; ASSERT_TRUE(app.Startup("")); app.SetRunningMode(); CSocketsChannelMgnt mgr; mgr.Initialize(""); mgr.SetOperationMode(sdv::EOperationMode::running); std::string longName(160, 'A'); const std::string rawPath = "C:\\Users\\" + longName + "\\AppData\\Local\\sdv\\vapi_win_long_" + RandomHex() + ".sock"; const std::string cs = std::string("proto=uds;path=") + rawPath + ";"; auto ep = mgr.CreateEndpoint(cs); ASSERT_FALSE(ep.ssConnectString.empty()); const std::string publishedCS = ep.ssConnectString; EXPECT_NE(publishedCS.find("path=vapi_win_long_"), std::string::npos); sdv::TObjectPtr sObj = mgr.Access(publishedCS); auto* server = sObj.GetInterface(); ASSERT_NE(server, nullptr); CTestReceiver sr; server->AsyncConnect(&sr); SpinUntilServerArmed(server); sdv::TObjectPtr cObj = mgr.Access(publishedCS); auto* client = cObj.GetInterface(); ASSERT_NE(client, nullptr); CTestReceiver cr; client->AsyncConnect(&cr); EXPECT_TRUE(server->WaitForConnection(5000)); EXPECT_TRUE(client->WaitForConnection(5000)); client->Disconnect(); server->Disconnect(); mgr.Shutdown(); app.Shutdown(); } #endif // _WIN32