Files
openvehicle-api/tests/unit_tests/unix_sockets/uds_connect_tests.cpp
2026-03-27 14:12:49 +01:00

1373 lines
47 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/********************************************************************************
* 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 __unix__
#include "gtest/gtest.h"
#include <support/app_control.h>
#include <interfaces/ipc.h>
#include "../../../sdv_services/uds_unix_sockets/channel_mgnt.cpp"
#include "../../../sdv_services/uds_unix_sockets/connection.cpp"
#include <thread>
#include <atomic>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <cstring>
#include <sstream>
#include <iomanip>
#include <random>
class CUDSConnectReceiver :
public sdv::IInterfaceAccess,
public sdv::ipc::IDataReceiveCallback,
public sdv::ipc::IConnectEventCallback
{
public:
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
END_SDV_INTERFACE_MAP()
// don't test data path yet
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>& /*seqData*/) override {}
void SetStatus(sdv::ipc::EConnectStatus s) override {
{
std::lock_guard<std::mutex> lk(m_mtx);
m_status = s;
}
m_cv.notify_all();
}
bool WaitForStatus(sdv::ipc::EConnectStatus expected, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
if (m_status == expected)
return true;
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms);
while (m_status != expected)
{
if (m_cv.wait_until(lk, deadline) == std::cv_status::timeout)
return false;
}
return true;
}
sdv::ipc::EConnectStatus GetStatus() const {
std::lock_guard<std::mutex> lk(m_mtx);
return m_status;
}
private:
sdv::ipc::EConnectStatus m_status { sdv::ipc::EConnectStatus::uninitialized };
mutable std::mutex m_mtx;
std::condition_variable m_cv;
};
// A data-aware receiver that captures received data chunks and exposes synchronization helpers.
class CUDSDataReceiver :
public sdv::IInterfaceAccess,
public sdv::ipc::IDataReceiveCallback,
public sdv::ipc::IConnectEventCallback
{
public:
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
END_SDV_INTERFACE_MAP()
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>& seqData) override {
{
std::lock_guard<std::mutex> lk(m_mtx);
m_lastData = seqData; // copy/move depending on sequence semantics
m_received = true;
}
m_cv.notify_all();
}
void SetStatus(sdv::ipc::EConnectStatus s) override {
{
std::lock_guard<std::mutex> lk(m_mtx);
m_status = s;
}
m_cv.notify_all();
}
bool WaitForStatus(sdv::ipc::EConnectStatus expected, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
if (m_status == expected)
return true;
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms);
while (m_status != expected)
{
if (m_cv.wait_until(lk, deadline) == std::cv_status::timeout)
return false;
}
return true;
}
bool WaitForData(uint32_t ms = 2000) {
std::unique_lock<std::mutex> lk(m_mtx);
return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
[&]{ return m_received; });
}
sdv::sequence<sdv::pointer<uint8_t>> GetLastData() const {
std::lock_guard<std::mutex> lk(m_mtx);
return m_lastData;
}
private:
mutable std::mutex m_mtx;
std::condition_variable m_cv;
sdv::ipc::EConnectStatus m_status{ sdv::ipc::EConnectStatus::uninitialized };
sdv::sequence<sdv::pointer<uint8_t>> m_lastData;
bool m_received{ false };
};
// A receiver that intentionally throws from SetStatus(...) to test callback-safety.
class CUDSThrowingReceiver :
public sdv::IInterfaceAccess,
public sdv::ipc::IDataReceiveCallback,
public sdv::ipc::IConnectEventCallback
{
public:
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
END_SDV_INTERFACE_MAP()
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>& /*seq*/) override {}
void SetStatus(sdv::ipc::EConnectStatus s) override
{
// Store the last status and then throw to simulate misbehaving user code.
{
std::lock_guard<std::mutex> lk(m_mtx);
m_last = s;
}
m_cv.notify_all();
throw std::runtime_error("Intentional user callback failure");
}
bool WaitForStatus(sdv::ipc::EConnectStatus expected, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
if (m_last == expected)
return true;
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms);
while (m_last != expected)
{
if (m_cv.wait_until(lk, deadline) == std::cv_status::timeout)
return false;
}
return true;
}
private:
std::mutex m_mtx;
std::condition_variable m_cv;
sdv::ipc::EConnectStatus m_last{ sdv::ipc::EConnectStatus::uninitialized };
};
// Small helper: convert server connectString to client connectString
static std::string MakeClientCS(std::string cs)
{
const std::string from = "role=server";
const std::string to = "role=client";
auto pos = cs.find(from);
if (pos != std::string::npos)
cs.replace(pos, from.size(), to);
return cs;
}
static std::string ExtractPathFromCS(const std::string& cs)
{
// search "path=" in connect-string
const std::string key = "path=";
auto p = cs.find(key);
if (p == std::string::npos) return {};
auto start = p + key.size();
auto end = cs.find(';', start);
if (end == std::string::npos) end = cs.size();
return cs.substr(start, end - start);
}
static std::string MakeRandomSuffix()
{
std::mt19937_64 rng{std::random_device{}()};
std::uniform_int_distribution<uint64_t> dist;
std::ostringstream oss;
oss << std::hex << dist(rng);
return oss.str();
}
TEST(UnixSocketIPC, Instantiate)
{
sdv::app::CAppControl appcontrol;
ASSERT_TRUE(appcontrol.Startup(""));
CUnixDomainSocketsChannelMgnt 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);
appcontrol.Shutdown();
}
TEST(UnixSocketIPC, ChannelConfigString)
{
sdv::app::CAppControl appcontrol;
ASSERT_TRUE(appcontrol.Startup(""));
CUnixDomainSocketsChannelMgnt 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);
}
TEST(UnixSocketIPC, CreateRandomEndpoint)
{
sdv::app::CAppControl appcontrol;
ASSERT_TRUE(appcontrol.Startup(""));
CUnixDomainSocketsChannelMgnt mgr;
// Create an endpoint.
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized);
sdv::ipc::SChannelEndpoint sChannelEndpoint = mgr.CreateEndpoint("");
EXPECT_NE(sChannelEndpoint.pConnection, nullptr);
EXPECT_FALSE(sChannelEndpoint.ssConnectString.empty());
if (sChannelEndpoint.pConnection) sdv::TObjectPtr(sChannelEndpoint.pConnection);
EXPECT_NO_THROW(mgr.Shutdown());
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);
}
// BASIC TEST: CreateEndpoint -> Access(server/client) -> AsyncConnect -> Wait -> Disconnect
TEST(UnixSocketIPC, BasicConnectDisconnect)
{
// Start SDV framework
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
// Create and initialize UDS manager
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// Create a Unix socket endpoint
auto ep = mgr.CreateEndpoint("");
ASSERT_FALSE(ep.ssConnectString.empty());
std::string serverCS = ep.ssConnectString;
std::string clientCS = MakeClientCS(serverCS);
// SERVER SIDE
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
sdv::ipc::IConnect* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CUDSConnectReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
// CLIENT SIDE (thread)
std::atomic<int> clientResult{0};
std::atomic<bool> allowClientDisconnect{false};
std::thread clientThread([&]{
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
if (!clientObj) { clientResult = 1; return; }
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
if (!clientConn) { clientResult = 2; return; }
CUDSConnectReceiver cRcvr;
if (!clientConn->AsyncConnect(&cRcvr)) { clientResult = 3; return; }
if (!clientConn->WaitForConnection(5000)) { clientResult = 4; return; }
if (clientConn->GetStatus() != sdv::ipc::EConnectStatus::connected) { clientResult = 5; return; }
// Wait for the server to be conected before disconecting the client
while (!allowClientDisconnect.load())
std::this_thread::sleep_for(std::chrono::milliseconds(10));
clientConn->Disconnect();
clientResult = 0;
});
// SERVER must also report connected
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::connected);
// Allow client to dissconect now, because the Server is connected
allowClientDisconnect = true;
clientThread.join();
EXPECT_EQ(clientResult.load(), 0);
//DISCONNECT both
serverConn->Disconnect();
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
// Shutdown Manager / Framework
EXPECT_NO_THROW(mgr.Shutdown());
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);
std::cout << "Shutdown Manager ok " << std::endl;
app.Shutdown();
}
TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath)
{
// Start SDV
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
//UDS manager
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
//Endpoint -> same path for both sessions
auto ep = mgr.CreateEndpoint("");
ASSERT_FALSE(ep.ssConnectString.empty());
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
// SESSION 1
// SERVER
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
sdv::ipc::IConnect* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CUDSConnectReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
// CLIENT (thread)
std::atomic<int> clientResult{0};
std::atomic<bool> allowClientDisconnect{false};
std::thread clientThread([&]{
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
if (!clientObj) { clientResult = 1; return; }
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
if (!clientConn) { clientResult = 2; return; }
CUDSConnectReceiver cRcvr;
if (!clientConn->AsyncConnect(&cRcvr)) { clientResult = 3; return; }
if (!clientConn->WaitForConnection(5000)) { clientResult = 4; return; }
if (clientConn->GetStatus() != sdv::ipc::EConnectStatus::connected) { clientResult = 5; return; }
//waits for confirmation before disconect
while (!allowClientDisconnect.load())
std::this_thread::sleep_for(std::chrono::milliseconds(10));
clientConn->Disconnect();
clientResult = 0;
});
// Server has to be connected (same timeout with client?)
EXPECT_TRUE(serverConn->WaitForConnection(5000)) << "Server didn't reach 'connected' in time";
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::connected);
// Allows Client to disconnect and waits for finishing
allowClientDisconnect = true;
clientThread.join();
EXPECT_EQ(clientResult.load(), 0);
// Disconnect server
serverConn->Disconnect();
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
// SESSION 2
// SERVER
sdv::TObjectPtr serverObj2 = mgr.Access(serverCS);
ASSERT_TRUE(serverObj2);
sdv::ipc::IConnect* serverConn2 = serverObj2.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn2, nullptr);
CUDSConnectReceiver sRcvr2;
ASSERT_TRUE(serverConn2->AsyncConnect(&sRcvr2));
// CLIENT (thread)
std::atomic<int> clientResult2{0};
std::atomic<bool> allowClientDisconnect2{false};
std::thread clientThread2([&]{
sdv::TObjectPtr clientObj2 = mgr.Access(clientCS);
if (!clientObj2) { clientResult2 = 1; return; }
auto* clientConn2 = clientObj2.GetInterface<sdv::ipc::IConnect>();
if (!clientConn2) { clientResult2 = 2; return; }
CUDSConnectReceiver cRcvr2;
if (!clientConn2->AsyncConnect(&cRcvr2)) { clientResult2 = 3; return; }
if (!clientConn2->WaitForConnection(5000)) { clientResult2 = 4; return; }
if (clientConn2->GetStatus() != sdv::ipc::EConnectStatus::connected) { clientResult2 = 5; return; }
//waits for confirmation before disconect
while (!allowClientDisconnect2.load())
std::this_thread::sleep_for(std::chrono::milliseconds(10));
clientConn2->Disconnect();
clientResult2 = 0;
});
// Server has to be connected
// if unlink(path) from session 1 worked, bind/listen/accept works again
EXPECT_TRUE(serverConn2->WaitForConnection(5000)) << "Server didn't reach 'connected' in time (session 2)";
EXPECT_EQ(serverConn2->GetStatus(), sdv::ipc::EConnectStatus::connected);
// Allows Client to disconnect and waits for finishing
allowClientDisconnect2 = true;
clientThread2.join();
EXPECT_EQ(clientResult2.load(), 0);
// Disconnect server
serverConn2->Disconnect();
EXPECT_EQ(serverConn2->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
//Shutdown manager/framework
EXPECT_NO_THROW(mgr.Shutdown());
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);
app.Shutdown();
}
//Manager: Initialize -> configuring -> running -> Shutdown
TEST(UnixSocketIPC, OperationModeTransitions)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized);
// configuring and then running
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::configuring));
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::configuring);
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// Shutdown
EXPECT_NO_THROW(mgr.Shutdown());
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);
app.Shutdown();
}
// Endpoint from config TOML + long path(clamping) + success connecting
TEST(UnixSocketIPC, CreateEndpoint_WithConfigAndPathClamping)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// long path, deliberat > 108 (sun_path) — it will be cut in CreateEndpoint
std::string longName(160, 'A');
std::string longPath = std::string("/tmp/sdv/") + longName + "_" + MakeRandomSuffix() + ".sock";
std::ostringstream cfg;
cfg << "[IpcChannel]\n";
cfg << "Name = \"ignored_" << MakeRandomSuffix() << "\"\n";
cfg << "Path = \"" << longPath << "\"\n";
auto ep = mgr.CreateEndpoint(cfg.str());
ASSERT_FALSE(ep.ssConnectString.empty());
// Checking if endpoint is server type and has an ok path
std::string serverCS = ep.ssConnectString;
std::string clientCS = MakeClientCS(serverCS);
auto clampedPath = ExtractPathFromCS(serverCS);
ASSERT_FALSE(clampedPath.empty());
EXPECT_LT(clampedPath.size(), sizeof(sockaddr_un::sun_path)); // doar verificare generica
// Connecting
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CUDSConnectReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
ASSERT_TRUE(clientObj);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(clientConn, nullptr);
CUDSConnectReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_TRUE(clientConn->WaitForConnection(5000));
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(clientConn->GetStatus(), sdv::ipc::EConnectStatus::connected);
// Cleanup
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Access() with default paths on both ends
TEST(UnixSocketIPC, Access_DefaultPath_ServerClientConnect)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// Without "path=", both sides use the default MakeUserRuntimeDir()+"/UDS_auto.sock"
const std::string serverCS = "proto=uds;role=server;";
const std::string clientCS = "proto=uds;role=client;";
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CUDSConnectReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
ASSERT_TRUE(clientObj);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(clientConn, nullptr);
CUDSConnectReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_TRUE(clientConn->WaitForConnection(5000));
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(clientConn->GetStatus(), sdv::ipc::EConnectStatus::connected);
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//WaitForConnection(INFINITE): client delays by 1s, server waits indefinitely
TEST(UnixSocketIPC, WaitForConnection_InfiniteWait_SlowClient)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
auto ep = mgr.CreateEndpoint("");
ASSERT_FALSE(ep.ssConnectString.empty());
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CUDSConnectReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
std::thread delayedClient([&]{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
CUDSConnectReceiver cRcvr;
clientConn->AsyncConnect(&cRcvr);
clientConn->WaitForConnection(5000);
clientConn->Disconnect();
});
// INFINITE wait (0xFFFFFFFFu)
EXPECT_TRUE(serverConn->WaitForConnection(0xFFFFFFFFu));
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::connected);
// Cleanup
delayedClient.join();
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//WaitForConnection(0): immediate check, before and after connection
TEST(UnixSocketIPC, WaitForConnection_ZeroTimeout_BeforeAndAfter)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
auto ep = mgr.CreateEndpoint("");
ASSERT_FALSE(ep.ssConnectString.empty());
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
CUDSConnectReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
// before client: immediate check must be false
EXPECT_FALSE(serverConn->WaitForConnection(0));
// start client
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
CUDSConnectReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
// afterward the immediate check may still fail (race). Use normal wait for determinism.
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_TRUE(clientConn->WaitForConnection(5000));
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Only the client starts -> timeout & connection_error
TEST(UnixSocketIPC, ClientTimeout_NoServer)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// Unique path ensuring nothing is listening
const std::string path = std::string("/tmp/sdv/timeout_") + MakeRandomSuffix() + ".sock";
const std::string clientCS = std::string("proto=uds;role=client;path=") + path + ";";
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
ASSERT_TRUE(clientObj);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(clientConn, nullptr);
CUDSConnectReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
// client loop is ~2s; wait less and check it's still not connected
EXPECT_FALSE(clientConn->WaitForConnection(1500));
// after another ~1s it should be in connection_error
std::this_thread::sleep_for(std::chrono::milliseconds(800));
EXPECT_EQ(clientConn->GetStatus(), sdv::ipc::EConnectStatus::connection_error);
clientConn->Disconnect(); // cleanup (joins threads)
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Server disconnecting -> client transitions to 'disconnected'
TEST(UnixSocketIPC, ServerDisconnectPropagatesToClient)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
auto ep = mgr.CreateEndpoint("");
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
CUDSConnectReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
CUDSConnectReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
ASSERT_TRUE(serverConn->WaitForConnection(5000));
ASSERT_TRUE(clientConn->WaitForConnection(5000));
// Break the server side and give the client receiver thread time to see EOF
serverConn->Disconnect();
// Deterministic wait for client-side transition to 'disconnected'
EXPECT_TRUE(cRcvr.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, /*ms*/ 3000)) << "Client did not observe 'disconnected' after server closed the socket.";
EXPECT_EQ(clientConn->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
clientConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Reconnecting on the same server object
TEST(UnixSocketIPC, ReconnectOnSameServerInstance)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
auto ep = mgr.CreateEndpoint("");
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
CUDSConnectReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
// First session
{
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
CUDSConnectReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
ASSERT_TRUE(serverConn->WaitForConnection(5000));
ASSERT_TRUE(clientConn->WaitForConnection(5000));
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
}
// Second session on the same serverConn object
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
{
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
CUDSConnectReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
ASSERT_TRUE(serverConn->WaitForConnection(5000));
ASSERT_TRUE(clientConn->WaitForConnection(5000));
clientConn->Disconnect();
}
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Simple payload "hello" test
TEST(UnixSocketIPC, DataPath_SimpleHello)
{
// Framework + Manager
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// Endpoint
auto ep = mgr.CreateEndpoint("");
ASSERT_FALSE(ep.ssConnectString.empty());
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
// Server
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CUDSDataReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
// Client
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
ASSERT_TRUE(clientObj);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
//sdv::ipc::IDataSend* clientConn = clientObj.GetInterface<sdv::ipc::IDataSend>();
ASSERT_NE(clientConn, nullptr);
CUDSDataReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
// Wait for both to be connected
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_TRUE(clientConn->WaitForConnection(5000));
// Build "hello" payload
sdv::pointer<uint8_t> p;
p.resize(5);
std::memcpy(reinterpret_cast<void*>(p.get()), "hello", 5);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(p);
// Send from client -> receive on server
auto* pSend = dynamic_cast<sdv::ipc::IDataSend*>(clientConn);
ASSERT_NE(pSend, nullptr);
EXPECT_TRUE(pSend->SendData(seq));
// Wait deterministically for server-side data callback
EXPECT_TRUE(sRcvr.WaitForData(3000));
auto recv = sRcvr.GetLastData();
ASSERT_EQ(recv.size(), 1u);
ASSERT_EQ(recv[0].size(), 5u);
EXPECT_EQ(std::memcmp(recv[0].get(), "hello", 5), 0);
// Cleanup
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Multichunk payload (2 chunks)
TEST(UnixSocketIPC, DataPath_MultiChunk_TwoBuffers)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
auto ep = mgr.CreateEndpoint("");
ASSERT_FALSE(ep.ssConnectString.empty());
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
CUDSDataReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
CUDSDataReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_TRUE(clientConn->WaitForConnection(5000));
// Two buffers: "sdv" and "framewrok"
sdv::pointer<uint8_t> p1, p2;
p1.resize(3);
std::memcpy(reinterpret_cast<void*>(p1.get()), "sdv", 3);
p2.resize(9);
std::memcpy(reinterpret_cast<void*>(p2.get()), "framework", 9);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(p1);
seq.push_back(p2);
// Send from client -> receive on server
auto* pSend = dynamic_cast<sdv::ipc::IDataSend*>(clientConn);
ASSERT_NE(pSend, nullptr);
EXPECT_TRUE(pSend->SendData(seq));
EXPECT_TRUE(sRcvr.WaitForData(3000));
auto recv = sRcvr.GetLastData();
ASSERT_EQ(recv.size(), 2u);
EXPECT_EQ(recv[0].size(), 3u);
EXPECT_EQ(recv[1].size(), 9u);
EXPECT_EQ(std::memcmp(recv[0].get(), "sdv", 3), 0);
EXPECT_EQ(std::memcmp(recv[1].get(), "framework", 9), 0);
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Large payload -> fragmented receive (endtoend reassembly)
TEST(UnixSocketIPC, DataPath_LargePayload_Fragmentation_Reassembly)
{
// Framework + Manager
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// Endpoint
auto ep = mgr.CreateEndpoint("");
ASSERT_FALSE(ep.ssConnectString.empty());
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
// Server
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CUDSDataReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
// Client
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
ASSERT_TRUE(clientObj);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(clientConn, nullptr);
CUDSDataReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
// Wait until both connected
ASSERT_TRUE(serverConn->WaitForConnection(5000));
ASSERT_TRUE(clientConn->WaitForConnection(5000));
// Build a large payload (e.g., 256 KiB) to force fragmentation in SendData(...)
const size_t totalBytes = 256 * 1024;
sdv::pointer<uint8_t> big;
big.resize(totalBytes);
// Fill with a deterministic pattern for verification
for (size_t i = 0; i < totalBytes; ++i)
{
big.get()[i] = static_cast<uint8_t>(i & 0xFF);
}
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(big);
auto* pSend = dynamic_cast<sdv::ipc::IDataSend*>(clientConn);
ASSERT_NE(pSend, nullptr);
EXPECT_TRUE(pSend->SendData(seq)); // SendData will fragment as needed
// Wait deterministically for server-side ReceiveData(...)
ASSERT_TRUE(sRcvr.WaitForData(5000));
auto recv = sRcvr.GetLastData();
ASSERT_EQ(recv.size(), 1u);
ASSERT_EQ(recv[0].size(), totalBytes);
EXPECT_EQ(std::memcmp(recv[0].get(), big.get(), totalBytes), 0);
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Zerolength chunks mixed with nonzero chunks
TEST(UnixSocketIPC, DataPath_ZeroLengthChunks_ArePreserved)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
auto ep = mgr.CreateEndpoint("");
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
CUDSDataReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
CUDSDataReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
ASSERT_TRUE(serverConn->WaitForConnection(5000));
ASSERT_TRUE(clientConn->WaitForConnection(5000));
// Build sequence: [0][5][""] [8]
sdv::pointer<uint8_t> p0, p5, p0b, p8;
p0.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<sdv::pointer<uint8_t>> seq;
seq.push_back(p0);
seq.push_back(p5);
seq.push_back(p0b);
seq.push_back(p8);
auto* pSend = dynamic_cast<sdv::ipc::IDataSend*>(clientConn);
ASSERT_NE(pSend, nullptr);
EXPECT_TRUE(pSend->SendData(seq)); // table includes zero sizes; receiver must see them
ASSERT_TRUE(sRcvr.WaitForData(3000));
auto recv = sRcvr.GetLastData();
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);
EXPECT_EQ(std::memcmp(recv[1].get(), "world", 5), 0);
EXPECT_EQ(std::memcmp(recv[3].get(), "fragment", 8), 0);
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown()); app.Shutdown();
}
//Peer closes midtransfer -> SendData fails and client observes disconnected
TEST(UnixSocketIPC, PeerCloseMidTransfer_ClientSeesDisconnected_AndSendMayFailOrSucceed)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
ASSERT_NO_THROW(mgr.Initialize(""));
ASSERT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
auto ep = mgr.CreateEndpoint("");
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
CUDSConnectReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
CUDSConnectReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
ASSERT_TRUE(serverConn->WaitForConnection(5000));
ASSERT_TRUE(clientConn->WaitForConnection(5000));
// Large buffer
sdv::pointer<uint8_t> big;
big.resize(512 * 1024);
memset(big.get(), 0xAA, big.size());
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(big);
auto* pSend = dynamic_cast<sdv::ipc::IDataSend*>(clientConn);
ASSERT_NE(pSend, nullptr);
std::atomic<bool> sendResult{true};
std::thread t([&]{
sendResult.store(pSend->SendData(seq));
});
std::this_thread::sleep_for(std::chrono::milliseconds(50));
serverConn->Disconnect();
t.join();
// We no longer EXPECT false:
// SendData may return true OR false depending on kernel timing.
// Just log the value for debugging:
std::cout << "[Debug] SendData result = " << sendResult.load() << std::endl;
// But client MUST observe disconnected:
EXPECT_TRUE(cRcvr.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 3000));
clientConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Client cancels connect (no server) -> clean stop of worker threads
TEST(UnixSocketIPC, ClientCancelConnect_NoServer_CleansUpPromptly)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// Unique path with no server listening
const std::string path = std::string("/tmp/sdv/cancel_") + MakeRandomSuffix() + ".sock";
const std::string clientCS = std::string("proto=uds;role=client;path=") + path + ";";
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
ASSERT_TRUE(clientObj);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(clientConn, nullptr);
CUDSConnectReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
// Cancel before the built-in retry loop completes.
std::this_thread::sleep_for(std::chrono::milliseconds(150));
clientConn->Disconnect();
// Immediately after disconnect, status should be 'disconnected' (no hangs).
EXPECT_EQ(clientConn->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_NO_THROW(mgr.Shutdown()); app.Shutdown();
}
//Server starts, then immediately disconnects (no client yet)
TEST(UnixSocketIPC, ServerStartThenImmediateDisconnect_NoClient)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
auto ep = mgr.CreateEndpoint("");
const std::string serverCS = ep.ssConnectString;
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CUDSConnectReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
// The server may be still listening; ensure we can disconnect cleanly
std::this_thread::sleep_for(std::chrono::milliseconds(50));
serverConn->Disconnect();
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Callback throws in SetStatus -> transport threads keep running
TEST(UnixSocketIPC, CallbackThrowsInSetStatus_DoesNotCrashTransport)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
auto ep = mgr.CreateEndpoint("");
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CUDSThrowingReceiver sRcvr; ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
ASSERT_TRUE(clientObj);
auto* clientConn = clientObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(clientConn, nullptr);
CUDSThrowingReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
// Despite exceptions thrown inside SetStatus, the transport should still reach connected.
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_TRUE(clientConn->WaitForConnection(5000));
// And it should still propagate a clean disconnect (even with throwing callbacks).
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//RegisterStatusEventCallback: multiple listeners receive status updates
TEST(UnixSocketIPC, RegisterStatusEventCallback_MultipleCallbacksReceiveStatus)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
ASSERT_NO_THROW(mgr.Initialize(""));
ASSERT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// --- Setup server endpoint ---
auto ep = mgr.CreateEndpoint("");
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
auto* server = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(server, nullptr);
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
auto* client = clientObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(client, nullptr);
// --- Callback receiver 1 ---
CUDSConnectReceiver recv1;
uint64_t cookie1 = server->RegisterStatusEventCallback(&recv1);
EXPECT_NE(cookie1, 0u);
// --- Callback receiver 2 ---
CUDSConnectReceiver recv2;
uint64_t cookie2 = server->RegisterStatusEventCallback(&recv2);
EXPECT_NE(cookie2, 0u);
EXPECT_NE(cookie1, cookie2);
// --- Start connections ---
ASSERT_TRUE(server->AsyncConnect(&recv1));
ASSERT_TRUE(client->AsyncConnect(&recv2));
ASSERT_TRUE(server->WaitForConnection(5000));
ASSERT_TRUE(client->WaitForConnection(5000));
// Both receivers should have received 'connected'
EXPECT_TRUE(recv1.WaitForStatus(sdv::ipc::EConnectStatus::connected, 1000));
EXPECT_TRUE(recv2.WaitForStatus(sdv::ipc::EConnectStatus::connected, 1000));
// --- Disconnect ---
client->Disconnect();
EXPECT_TRUE(recv1.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 1000));
EXPECT_TRUE(recv2.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 1000));
server->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//UnregisterStatusEventCallback: removed listener stops receiving events
TEST(UnixSocketIPC, UnregisterStatusEventCallback_RemovedListenerStopsReceiving)
{
// Framework + Manager
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
ASSERT_NO_THROW(mgr.Initialize(""));
ASSERT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// Endpoint
auto ep = mgr.CreateEndpoint("");
const std::string serverCS = ep.ssConnectString;
const std::string clientCS = MakeClientCS(serverCS);
// Server
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
auto* server = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(server, nullptr);
// Client
sdv::TObjectPtr clientObj = mgr.Access(clientCS);
ASSERT_TRUE(clientObj);
auto* client = clientObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(client, nullptr);
// --------- Two distinct receivers ----------
// recvReg: used ONLY for the status-callback registry
// recvConn: used ONLY for AsyncConnect (m_pReceiver / m_pEvent path)
CUDSConnectReceiver recvReg;
CUDSConnectReceiver recvConn;
// Register recvReg as a status listener (registry path)
uint64_t cookie = server->RegisterStatusEventCallback(&recvReg);
ASSERT_NE(cookie, 0u) << "Cookie must be non-zero";
// Start connections (server uses recvReg only for registry; recvConn is the IConnect/IDataReceive side)
ASSERT_TRUE(server->AsyncConnect(&recvConn));
ASSERT_TRUE(client->AsyncConnect(&recvConn));
// Wait until both sides are connected
ASSERT_TRUE(server->WaitForConnection(5000));
ASSERT_TRUE(client->WaitForConnection(5000));
// Both the connection receiver (recvConn) and the registry listener (recvReg) should observe 'connected'
EXPECT_TRUE(recvConn.WaitForStatus(sdv::ipc::EConnectStatus::connected, 1000)) << "Connection receiver didn't see 'connected'.";
EXPECT_TRUE(recvReg.WaitForStatus(sdv::ipc::EConnectStatus::connected, 1000)) << "Registry listener didn't see 'connected'.";
// --------- Unregister the registry listener ----------
server->UnregisterStatusEventCallback(cookie);
// Trigger a disconnect on client to force status transitions
client->Disconnect();
// The connection receiver (recvConn) still sees disconnected (normal path)
EXPECT_TRUE(recvConn.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 1000)) << "Connection receiver didn't see 'disconnected'.";
// The registry listener (recvReg) MUST NOT receive 'disconnected' anymore
// (wait a short, deterministic interval)
EXPECT_FALSE(recvReg.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 300)) << "Registry listener received 'disconnected' after UnregisterStatusEventCallback().";
// Server cleanup
server->Disconnect();
// Manager / framework cleanup
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
#endif // defined __unix__