tunnel component & update vehicle abstraction example (#8)

This commit is contained in:
tompzf
2026-04-02 17:37:00 +02:00
committed by GitHub
parent 6ed5fdb951
commit 07cf4f654b
94 changed files with 9268 additions and 830 deletions

View File

@@ -0,0 +1,107 @@
#*******************************************************************************
# 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
#
# Contributors:
# Denisa Ros - initial API and implementation
#*******************************************************************************
if(UNIX)
cmake_minimum_required(VERSION 3.20)
project(UnixTunnelCommunicationTests LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Executable: GTest for UDS connect
add_executable(UnitTest_UnixTunnelConnectTests
unix_tunnel_connect_tests.cpp
)
target_include_directories(UnitTest_UnixTunnelConnectTests PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_link_libraries(UnitTest_UnixTunnelConnectTests
GTest::GTest
${CMAKE_THREAD_LIBS_INIT}
stdc++fs
${CMAKE_DL_LIBS}
rt
uds_unix_tunnel
uds_unix_sockets
)
else()
target_link_libraries(UnitTest_UnixTunnelConnectTests
GTest::GTest
Ws2_32 Winmm
Rpcrt4.lib
uds_unix_tunnel
uds_unix_sockets
)
endif()
add_test(NAME UnitTest_UnixTunnelConnectTests
COMMAND UnitTest_UnixTunnelConnectTests)
add_dependencies(UnitTest_UnixTunnelConnectTests dependency_sdv_components)
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
add_custom_command(TARGET UnitTest_UnixTunnelConnectTests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake
"$<TARGET_FILE:UnitTest_UnixTunnelConnectTests>"
--gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_UnixTunnelConnectTests.xml
VERBATIM
)
endif()
add_executable(UnitTest_UnixTunnelChannelMgntTests
unix_tunnel_channel_mgnt_tests.cpp
)
target_include_directories(UnitTest_UnixTunnelChannelMgntTests PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_link_libraries(UnitTest_UnixTunnelChannelMgntTests
GTest::GTest
${CMAKE_THREAD_LIBS_INIT}
stdc++fs
${CMAKE_DL_LIBS}
rt
uds_unix_tunnel
uds_unix_sockets
)
else()
target_link_libraries(UnitTest_UnixTunnelChannelMgntTests
GTest::GTest
Ws2_32
Winmm
Rpcrt4.lib
uds_unix_tunnel
uds_unix_sockets
)
endif()
add_test(NAME UnitTest_UnixTunnelChannelMgntTests
COMMAND UnitTest_UnixTunnelChannelMgntTests)
add_dependencies(UnitTest_UnixTunnelChannelMgntTests dependency_sdv_components)
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
add_custom_command(TARGET UnitTest_UnixTunnelChannelMgntTests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake
"$<TARGET_FILE:UnitTest_UnixTunnelChannelMgntTests>"
--gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_UnixTunnelChannelMgntTests.xml
VERBATIM
)
endif()
endif()

View File

@@ -0,0 +1,240 @@
#if defined(__unix__)
#include "gtest/gtest.h"
#include <support/app_control.h>
#include <interfaces/ipc.h>
#include <support/interface_ptr.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <mutex>
#include <thread>
#include "../sdv_services/uds_unix_tunnel/channel_mgnt.h"
#include "../sdv_services/uds_unix_tunnel/connection.h"
#include "../sdv_services/uds_unix_sockets/connection.h"
// Reuse the same CTunnelTestReceiver from unix_tunnel_connect_tests
class CTunnelMgrTestReceiver :
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;
m_received = true;
}
m_cv.notify_all();
}
void SetConnectState(sdv::ipc::EConnectState s) override
{
{
std::lock_guard<std::mutex> lk(m_mtx);
m_state = s;
}
m_cv.notify_all();
}
bool WaitForState(sdv::ipc::EConnectState expected, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
if (m_state == expected)
return true;
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms);
while (m_state != 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::EConnectState m_state{ sdv::ipc::EConnectState::uninitialized };
sdv::sequence<sdv::pointer<uint8_t>> m_lastData;
bool m_received{ false };
};
//Manager instantiate + lifecycle
TEST(UnixTunnelChannelMgnt, InstantiateAndLifecycle)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
CUnixTunnelChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized);
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);
EXPECT_NO_THROW(mgr.Shutdown());
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);
app.Shutdown();
}
// CreateEndpoint -> Access(server/client) -> AsyncConnect -> Wait -> Disconnect
TEST(UnixTunnelChannelMgnt, BasicConnectDisconnect)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixTunnelChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_NO_THROW(mgr.SetOperationMode(sdv::EOperationMode::running));
ASSERT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
// Create a tunnel endpoint (server)
auto ep = mgr.CreateEndpoint("");
ASSERT_NE(ep.pConnection, nullptr);
ASSERT_FALSE(ep.ssConnectString.empty());
const std::string serverCS = ep.ssConnectString;
// Convert to client by role=client
std::string clientCS = serverCS;
{
const std::string from = "role=server";
const std::string to = "role=client";
auto pos = clientCS.find(from);
if (pos != std::string::npos)
clientCS.replace(pos, from.size(), to);
}
// SERVER
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CTunnelMgrTestReceiver sRcvr;
ASSERT_TRUE(serverConn->AsyncConnect(&sRcvr));
// CLIENT (thread)
std::atomic<int> clientResult{0};
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; }
CTunnelMgrTestReceiver cRcvr;
if (!clientConn->AsyncConnect(&cRcvr)) { clientResult = 3; return; }
if (!clientConn->WaitForConnection(5000)) { clientResult = 4; return; }
if (clientConn->GetConnectState() != sdv::ipc::EConnectState::connected) { clientResult = 5; return; }
clientConn->Disconnect();
clientResult = 0;
});
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::connected);
clientThread.join();
EXPECT_EQ(clientResult.load(), 0);
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
// Data path: "hello" via channel manager (using proto=tunnel)
TEST(UnixTunnelChannelMgnt, DataPath_SimpleHello_ViaManager)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CUnixTunnelChannelMgnt 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;
std::string clientCS = serverCS;
{
const std::string from = "role=server";
const std::string to = "role=client";
auto pos = clientCS.find(from);
if (pos != std::string::npos)
clientCS.replace(pos, from.size(), to);
}
// Server
sdv::TObjectPtr serverObj = mgr.Access(serverCS);
ASSERT_TRUE(serverObj);
auto* serverConn = serverObj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(serverConn, nullptr);
CTunnelMgrTestReceiver 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);
CTunnelMgrTestReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_TRUE(clientConn->WaitForConnection(5000));
// Payload "hello"
sdv::pointer<uint8_t> p;
p.resize(5);
std::memcpy(p.get(), "hello", 5);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(p);
auto* pSend = dynamic_cast<sdv::ipc::IDataSend*>(clientConn);
ASSERT_NE(pSend, nullptr);
EXPECT_TRUE(pSend->SendData(seq));
EXPECT_TRUE(sRcvr.WaitForData(3000));
sdv::sequence<sdv::pointer<uint8_t>> recv = sRcvr.GetLastData();
ASSERT_EQ(recv.size(), 1u);
ASSERT_EQ(recv[0].size(), 5u);
EXPECT_EQ(std::memcmp(recv[0].get(), "hello", 5), 0);
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
#endif // defined(__unix__)

View File

@@ -0,0 +1,376 @@
/********************************************************************************
* 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
*
* Contributors:
* Denisa Ros - initial API and implementation
********************************************************************************/
#if defined(__unix__)
#include "gtest/gtest.h"
#include <interfaces/ipc.h>
#include <support/interface_ptr.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <mutex>
#include <random>
#include <sstream>
#include <string>
#include <thread>
// Adjust include paths to your tree layout:
#include "../sdv_services/uds_unix_sockets/connection.h" // CUnixSocketConnection
#include "../sdv_services/uds_unix_tunnel/connection.h" // CUnixTunnelConnection
// ===================== Test helpers =====================
class CTunnelTestReceiver :
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()
// IDataReceiveCallback
void ReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override
{
{
std::lock_guard<std::mutex> lk(m_mtx);
m_lastData = seqData;
m_received = true;
}
m_cv.notify_all();
}
// IConnectEventCallback
void SetConnectState(sdv::ipc::EConnectState s) override
{
{
std::lock_guard<std::mutex> lk(m_mtx);
m_state = s;
}
m_cv.notify_all();
}
bool WaitForState(sdv::ipc::EConnectState expected, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
if (m_state == expected)
return true;
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms);
while (m_state != 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;
}
sdv::ipc::EConnectState GetConnectState() const
{
std::lock_guard<std::mutex> lk(m_mtx);
return m_state;
}
private:
mutable std::mutex m_mtx;
std::condition_variable m_cv;
sdv::ipc::EConnectState m_state { sdv::ipc::EConnectState::uninitialized };
sdv::sequence<sdv::pointer<uint8_t>> m_lastData;
bool m_received { false };
};
// Small helper similar to MakeRandomSuffix() in uds_connect_tests.cpp
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();
}
// ===================== Tests =====================
// BASIC: server + client CUnixSocketConnection wrapped by CUnixTunnelConnection.
// Just test connect + disconnect via tunnel.
TEST(UnixTunnelIPC, BasicConnectDisconnectViaTunnel)
{
const std::string udsPath = std::string("/tmp/sdv_tunnel_") + MakeRandomSuffix() + ".sock";
// --- Physical transports (UDS) ---
auto serverTransport = std::make_shared<CUnixSocketConnection>(
-1, /*preconfiguredFd*/
true, /*acceptConnectionRequired (server)*/
udsPath);
auto clientTransport = std::make_shared<CUnixSocketConnection>(
-1,
false, /*client*/
udsPath);
// --- Tunnel wrappers ---
auto serverTunnel = std::make_shared<CUnixTunnelConnection>(
serverTransport,
/*channelId*/ 1);
auto clientTunnel = std::make_shared<CUnixTunnelConnection>(
clientTransport,
/*channelId*/ 1);
CTunnelTestReceiver serverRcvr;
CTunnelTestReceiver clientRcvr;
// Register state callbacks (optional, but useful)
uint64_t srvCookie = serverTunnel->RegisterStateEventCallback(&serverRcvr);
uint64_t cliCookie = clientTunnel->RegisterStateEventCallback(&clientRcvr);
EXPECT_NE(srvCookie, 0u);
EXPECT_NE(cliCookie, 0u);
// Async connect on both ends
ASSERT_TRUE(serverTunnel->AsyncConnect(&serverRcvr));
ASSERT_TRUE(clientTunnel->AsyncConnect(&clientRcvr));
// Wait for connections
EXPECT_TRUE(serverTunnel->WaitForConnection(5000));
EXPECT_TRUE(clientTunnel->WaitForConnection(5000));
EXPECT_EQ(serverTunnel->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(clientTunnel->GetConnectState(), sdv::ipc::EConnectState::connected);
// Disconnect client first
clientTunnel->Disconnect();
EXPECT_TRUE(clientRcvr.WaitForState(sdv::ipc::EConnectState::disconnected, 2000));
// Disconnect server
serverTunnel->Disconnect();
EXPECT_TRUE(serverRcvr.WaitForState(sdv::ipc::EConnectState::disconnected, 2000));
serverTunnel->UnregisterStateEventCallback(srvCookie);
clientTunnel->UnregisterStateEventCallback(cliCookie);
}
// DATA PATH: send "hello" via tunnel and verify it is received on server side.
// For now the tunnel is "pass-through" (no STunnelHeader yet).
TEST(UnixTunnelIPC, DataPath_SimpleHello_ViaTunnel)
{
const std::string udsPath = std::string("/tmp/sdv_tunnel_data_") + MakeRandomSuffix() + ".sock";
// Physical transports
auto serverTransport = std::make_shared<CUnixSocketConnection>(
-1, true, udsPath);
auto clientTransport = std::make_shared<CUnixSocketConnection>(
-1, false, udsPath);
// Tunnel wrappers (same logical channel for both ends)
auto serverTunnel = std::make_shared<CUnixTunnelConnection>(serverTransport, 1);
auto clientTunnel = std::make_shared<CUnixTunnelConnection>(clientTransport, 1);
CTunnelTestReceiver serverRcvr;
CTunnelTestReceiver clientRcvr;
ASSERT_TRUE(serverTunnel->AsyncConnect(&serverRcvr));
ASSERT_TRUE(clientTunnel->AsyncConnect(&clientRcvr));
ASSERT_TRUE(serverTunnel->WaitForConnection(5000));
ASSERT_TRUE(clientTunnel->WaitForConnection(5000));
// Build "hello" payload
sdv::sequence<sdv::pointer<uint8_t>> seq;
sdv::pointer<uint8_t> p;
const char* msg = "hello";
const size_t len = std::strlen(msg);
p.resize(len);
std::memcpy(p.get(), msg, len);
seq.push_back(p);
auto* pSend = dynamic_cast<sdv::ipc::IDataSend*>(clientTunnel.get());
ASSERT_NE(pSend, nullptr);
EXPECT_TRUE(pSend->SendData(seq));
// Wait for server-side data callback
ASSERT_TRUE(serverRcvr.WaitForData(3000));
sdv::sequence<sdv::pointer<uint8_t>> recv = serverRcvr.GetLastData();
ASSERT_EQ(recv.size(), 1u);
ASSERT_EQ(recv[0].size(), len);
EXPECT_EQ(std::memcmp(recv[0].get(), msg, len), 0);
clientTunnel->Disconnect();
serverTunnel->Disconnect();
}
TEST(UnixTunnelIPC, DataPath_MultiChunk_ViaTunnel)
{
const std::string udsPath = std::string("/tmp/sdv_tunnel_multi_") + MakeRandomSuffix() + ".sock";
auto serverTransport = std::make_shared<CUnixSocketConnection>(-1, true, udsPath);
auto clientTransport = std::make_shared<CUnixSocketConnection>(-1, false, udsPath);
auto serverTunnel = std::make_shared<CUnixTunnelConnection>(serverTransport, 1);
auto clientTunnel = std::make_shared<CUnixTunnelConnection>(clientTransport, 1);
CTunnelTestReceiver serverRcvr;
CTunnelTestReceiver clientRcvr;
ASSERT_TRUE(serverTunnel->AsyncConnect(&serverRcvr));
ASSERT_TRUE(clientTunnel->AsyncConnect(&clientRcvr));
ASSERT_TRUE(serverTunnel->WaitForConnection(5000));
ASSERT_TRUE(clientTunnel->WaitForConnection(5000));
// Two chunks: "sdv" and "framework"
sdv::pointer<uint8_t> p1, p2;
p1.resize(3);
std::memcpy(p1.get(), "sdv", 3);
p2.resize(9);
std::memcpy(p2.get(), "framework", 9);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(p1);
seq.push_back(p2);
auto* pSend = dynamic_cast<sdv::ipc::IDataSend*>(clientTunnel.get());
ASSERT_NE(pSend, nullptr);
EXPECT_TRUE(pSend->SendData(seq));
ASSERT_TRUE(serverRcvr.WaitForData(3000));
sdv::sequence<sdv::pointer<uint8_t>> recv = serverRcvr.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);
clientTunnel->Disconnect();
serverTunnel->Disconnect();
}
TEST(UnixTunnelIPC, DataPath_LargePayload_Fragmentation_ViaTunnel)
{
const std::string udsPath = std::string("/tmp/sdv_tunnel_large_") + MakeRandomSuffix() + ".sock";
auto serverTransport = std::make_shared<CUnixSocketConnection>(-1, true, udsPath);
auto clientTransport = std::make_shared<CUnixSocketConnection>(-1, false, udsPath);
auto serverTunnel = std::make_shared<CUnixTunnelConnection>(serverTransport, 1);
auto clientTunnel = std::make_shared<CUnixTunnelConnection>(clientTransport, 1);
CTunnelTestReceiver serverRcvr;
CTunnelTestReceiver clientRcvr;
ASSERT_TRUE(serverTunnel->AsyncConnect(&serverRcvr));
ASSERT_TRUE(clientTunnel->AsyncConnect(&clientRcvr));
ASSERT_TRUE(serverTunnel->WaitForConnection(5000));
ASSERT_TRUE(clientTunnel->WaitForConnection(5000));
// Build a large payload (e.g. 256 KiB)
const size_t totalBytes = 256 * 1024;
sdv::pointer<uint8_t> big;
big.resize(totalBytes);
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*>(clientTunnel.get());
ASSERT_NE(pSend, nullptr);
EXPECT_TRUE(pSend->SendData(seq));
ASSERT_TRUE(serverRcvr.WaitForData(5000));
sdv::sequence<sdv::pointer<uint8_t>> recv = serverRcvr.GetLastData();
ASSERT_EQ(recv.size(), 1u);
ASSERT_EQ(recv[0].size(), totalBytes);
EXPECT_EQ(std::memcmp(recv[0].get(), big.get(), totalBytes), 0);
clientTunnel->Disconnect();
serverTunnel->Disconnect();
}
// DATA PATH + HEADER: ensure that the upper receiver sees only the original
// payload (no STunnelHeader bytes in front)
TEST(UnixTunnelIPC, DataPath_HeaderStripped_ForUpperReceiver)
{
const std::string udsPath =
std::string("/tmp/sdv_tunnel_header_") + MakeRandomSuffix() + ".sock";
// Physical transports (UDS)
auto serverTransport = std::make_shared<CUnixSocketConnection>(
-1, /*server*/ true, udsPath);
auto clientTransport = std::make_shared<CUnixSocketConnection>(
-1, /*client*/ false, udsPath);
// Tunnel wrappers
auto serverTunnel = std::make_shared<CUnixTunnelConnection>(serverTransport, /*channelId*/ 42);
auto clientTunnel = std::make_shared<CUnixTunnelConnection>(clientTransport, /*channelId*/ 42);
CTunnelTestReceiver serverRcvr;
CTunnelTestReceiver clientRcvr;
ASSERT_TRUE(serverTunnel->AsyncConnect(&serverRcvr));
ASSERT_TRUE(clientTunnel->AsyncConnect(&clientRcvr));
ASSERT_TRUE(serverTunnel->WaitForConnection(5000));
ASSERT_TRUE(clientTunnel->WaitForConnection(5000));
// Build payload "HEADER_TEST"
const char* msg = "HEADER_TEST";
const size_t len = std::strlen(msg);
sdv::pointer<uint8_t> p;
p.resize(len);
std::memcpy(p.get(), msg, len);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(p);
auto* pSend = dynamic_cast<sdv::ipc::IDataSend*>(clientTunnel.get());
ASSERT_NE(pSend, nullptr);
EXPECT_TRUE(pSend->SendData(seq));
// Wait for server-side data callback
ASSERT_TRUE(serverRcvr.WaitForData(3000));
auto recv = serverRcvr.GetLastData();
// --- Assertions that header was stripped for upper receiver ---
// Upper receiver must see exactly one chunk with the original size, without extra bytes for STunnelHeader
ASSERT_EQ(recv.size(), 1u) << "Upper receiver must see exactly one payload chunk";
ASSERT_EQ(recv[0].size(), len) << "Payload size must equal original size (no header)";
EXPECT_EQ(std::memcmp(recv[0].get(), msg, len), 0)
<< "Payload content must match exactly the original message (no header in front)";
clientTunnel->Disconnect();
serverTunnel->Disconnect();
}
#endif // defined(__unix__)