mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-04-18 18:48:16 +00:00
1373 lines
47 KiB
C++
1373 lines
47 KiB
C++
/********************************************************************************
|
||
* 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();
|
||
}
|
||
|
||
//Multi‑chunk 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 (end‑to‑end 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();
|
||
}
|
||
|
||
//Zero‑length chunks mixed with non‑zero 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 mid‑transfer -> 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__
|