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

3
.gitignore vendored
View File

@@ -19,7 +19,7 @@ DoxygenWarning.txt
packages/
windows_vapi/.vs
#idl & dbc
#idl & dbc & vss
export/interfaces/*.h
export/interfaces/ps/
export/interfaces/serdes/
@@ -27,6 +27,7 @@ export/interfaces/serdes/
*/*/*/generated/
*/*/generated/
*/generated/
*/*/generated2/*
#CMake intermediate files and folders

View File

@@ -118,3 +118,4 @@ add_subdirectory(system_demo_example)
add_subdirectory(door_demo_example)
add_subdirectory(auto_headlamp_example)
add_subdirectory(open_trunk_example)
add_subdirectory(vehicle_abstraction_example)

View File

@@ -65,7 +65,7 @@ execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/autoheadlig
# REMARK: Proxy/stub and vehicle device and basic service code was generated during the configuration phase of CMake. Following
# below are the build steps to build the components that were generated.
message("Include: auto headlight proxy/stub for vehicle devices and basic services")
message("Include: auto headlight proxy/stub for basic services")
include_directories(${CMAKE_CURRENT_LIST_DIR}/generated/vss_files)
add_subdirectory(generated/vss_files/ps)
@@ -107,7 +107,6 @@ add_executable(auto_headlight_app
autoheadlight_app/autoheadlight_simulate.cpp
autoheadlight_app/autoheadlight_console.h
autoheadlight_app/autoheadlight_console.cpp
autoheadlight_app/signal_names.h
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")

View File

@@ -12,7 +12,7 @@
#include <fstream>
#include <support/app_control.h>
#include <support/signal_support.h>
#include "signal_names.h"
#include "../generated/vss_files/signal_identifier.h"
#include "autoheadlight_simulate.h"
#include "autoheadlight_console.h"

View File

@@ -108,14 +108,14 @@ void CConsole::PrintHeader()
bool CConsole::PrepareDataConsumers()
{
// Vehicle Device
auto pVDCurrentLatitudeSvc = sdv::core::GetObject("Vehicle.Position.CurrentLatitude_Device").GetInterface<vss::Vehicle::Position::CurrentLatitudeDevice::IVSS_CurrentLatitude>();
auto pVDCurrentLatitudeSvc = sdv::core::GetObject("Vehicle.Position.CurrentLatitude_Device").GetInterface<vss::Vehicle::Position::CurrentLatitudeDevice::IVSS_ReadCurrentLatitude>();
if (!pVDCurrentLatitudeSvc)
{
SDV_LOG_ERROR("Could not get interface 'IVSS_GetVDCurrentLatitude': [CAutoHeadlightService]");
return false;
}
auto pVDCurrentLongitudeSvc = sdv::core::GetObject("Vehicle.Position.CurrentLongitude_Device").GetInterface<vss::Vehicle::Position::CurrentLongitudeDevice::IVSS_CurrentLongitude>();
auto pVDCurrentLongitudeSvc = sdv::core::GetObject("Vehicle.Position.CurrentLongitude_Device").GetInterface<vss::Vehicle::Position::CurrentLongitudeDevice::IVSS_ReadCurrentLongitude>();
if (!pVDCurrentLongitudeSvc)
{
SDV_LOG_ERROR("Could not get interface 'IVSS_GetVDCurrentLongitude': [CAutoHeadlightService]");
@@ -212,11 +212,11 @@ void CConsole::ResetSignals()
SetCursorPos(g_sCursor);
// Vehicle Device
auto pVDCurrentLatitudeSvc = sdv::core::GetObject("Vehicle.Position.CurrentLatitude_Device").GetInterface<vss::Vehicle::Position::CurrentLatitudeDevice::IVSS_CurrentLatitude>();
auto pVDCurrentLatitudeSvc = sdv::core::GetObject("Vehicle.Position.CurrentLatitude_Device").GetInterface<vss::Vehicle::Position::CurrentLatitudeDevice::IVSS_ReadCurrentLatitude>();
if (pVDCurrentLatitudeSvc)
pVDCurrentLatitudeSvc->UnregisterCurrentLatitudeEvent(static_cast<vss::Vehicle::Position::CurrentLatitudeDevice::IVSS_WriteCurrentLatitude_Event*> (this));
auto pVDCurrentLongitudeSvc = sdv::core::GetObject("Vehicle.Position.CurrentLongitude_Device").GetInterface<vss::Vehicle::Position::CurrentLongitudeDevice::IVSS_CurrentLongitude>();
auto pVDCurrentLongitudeSvc = sdv::core::GetObject("Vehicle.Position.CurrentLongitude_Device").GetInterface<vss::Vehicle::Position::CurrentLongitudeDevice::IVSS_ReadCurrentLongitude>();
if (pVDCurrentLongitudeSvc)
pVDCurrentLongitudeSvc->UnregisterCurrentLongitudeEvent(static_cast<vss::Vehicle::Position::CurrentLongitudeDevice::IVSS_WriteCurrentLongitude_Event*> (this));

View File

@@ -19,7 +19,7 @@
#include <support/component_impl.h>
#include <support/timer.h>
#include <atomic>
#include "signal_names.h"
#include "../generated/vss_files/signal_identifier.h"
#include <fcntl.h>
#include "vss_vehiclepositioncurrentlatitude_vd_rx.h"
#include "vss_vehiclepositioncurrentlongitude_vd_rx.h"
@@ -187,7 +187,7 @@ private:
* @param[in] sPos The location to print the value at.
* @param[in] rssName Reference to the value.
* @param[in] tValue The value.
* @param[in] rssStatus Status, becuse we have signals of type bool
* @param[in] rssStatus Status, because we have signals of type bool
*/
template <typename TValue>
void PrintValue(SConsolePos sPos, const std::string& rssName, TValue tValue, const std::string& rssStatus);
@@ -204,7 +204,6 @@ private:
std::thread m_threadReadTxSignals; ///< Simulation datalink thread.
bool m_bThreadStarted = false; ///< Set when initialized.
std::atomic_bool m_bRunning = false; ///< When set, the application is running.
mutable std::mutex m_mPrintToConsole; ///< Mutex to print complete message
sdv::core::CSignal m_signalCurrentLatitude; ///< Signal Current latitude
sdv::core::CSignal m_signalCurrentLongitude; ///< Signal Current longitude
@@ -244,7 +243,7 @@ inline void CConsole::PrintValue(SConsolePos sPos, const std::string& rssName, T
std::string(nValueNameLen - std::min(rssName.size(), static_cast<size_t>(nValueNameLen - 1)) - 1, '.') <<
" " << std::fixed << std::setprecision(6) << tValue << " " << rssUnits << " ";
std::lock_guard<std::mutex> lock(m_mPrintToConsole);
std::lock_guard<std::mutex> lock(m_mtxPrintToConsole);
SetCursorPos(sPos);
std::cout << sstreamValueText.str();
}

View File

@@ -15,7 +15,7 @@
#include <support/app_control.h>
#include <support/component_impl.h>
#include <support/timer.h>
#include "signal_names.h"
#include "../generated/vss_files/signal_identifier.h"
// VSS interfaces - located in ../generated/vss_files/include
#include "vss_vehiclepositioncurrentlatitude_bs_rx.h"

View File

@@ -1,35 +0,0 @@
/********************************************************************************
* 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
********************************************************************************/
/**
* namespace for the signal names
* in case /generated/vss_files/signal_identifier.h
* exists, use the file, otherwise define the namespace
*/
#ifndef SIGNAL_NAMES_H
#define SIGNAL_NAMES_H
#ifdef __has_include
#if __has_include("../generated/vss_files/signal_identifier.h")
#include "../generated/vss_files/signal_identifier.h"
#else
namespace headlight
{
// Data Dispatch Service signal names to dbc variable names C-type RX/TX vss name space
static std::string dsFCurrentLatitude = "CAN_Input.Current_Latitude" ; ///< float RX Vehicle.Position.CurrentLatitude
static std::string dsFCurrentLongitude = "CAN_Input.Current_Longitude" ; ///< float RX Vehicle.Position.CurrentLongitude
static std::string dsBHeadlightLowBeam = "CAN_Output.HeadLight_LowBeam"; ///< bool TX Vehicle.Body.Light.Front.Lowbeam
} // headlight
#endif
#endif
#endif // ! defined SIGNAL_NAMES_H

View File

@@ -132,7 +132,6 @@ add_executable(door_demo_example
"door_app/door_application.cpp"
"door_app/include/console.h"
"door_app/console.cpp"
"door_app/include/signal_names.h"
"door_service/lock_doors_thread.h")
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
@@ -156,7 +155,6 @@ add_executable(door_external_app
"door_app/door_extern_application.cpp"
"door_app/include/console.h"
"door_app/console.cpp"
"door_app/include/signal_names.h"
"door_service/lock_doors_thread.h")
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")

View File

@@ -144,7 +144,7 @@ bool CConsole::PrepareDataConsumers()
auto basicServiceL1 = sdv::core::GetObject("Vehicle.Body.Door.Axle._01.Left_Service").GetInterface<vss::Vehicle::Body::Door::Axle::_01::LeftService::IVSS_GetIsOpenL1>();
if (!basicServiceL1)
{
SDV_LOG_ERROR("Could not get interface 'LeftService::IVSS_IsOpen': [CConsole]");
SDV_LOG_ERROR("Could not get interface 'LeftService::IVSS_ReadIsOpen': [CConsole]");
return false;
}
else
@@ -207,7 +207,7 @@ void CConsole::ResetSignals()
// Registrate for the vehicle device & basic service of the front left door. Front left door mzust exist, the others are optional
auto vehicleDevice =
sdv::core::GetObject("Vehicle.Body.Door.Axle._01.Left_Device").GetInterface < vss::Vehicle::Body::Door::Axle::_01 ::LeftDevice::IVSS_IsOpen > ();
sdv::core::GetObject("Vehicle.Body.Door.Axle._01.Left_Device").GetInterface < vss::Vehicle::Body::Door::Axle::_01 ::LeftDevice::IVSS_ReadIsOpen > ();
if (vehicleDevice)
vehicleDevice->UnregisterIsOpenEvent(dynamic_cast<vss::Vehicle::Body::Door::Axle::_01::LeftDevice::IVSS_WriteIsOpen_Event*> (this));
@@ -227,7 +227,7 @@ void CConsole::ResetSignals()
if (basicServiceR2)
basicServiceR2->UnregisterOnSignalChangeOfRightDoorIsOpen02(dynamic_cast<vss::Vehicle::Body::Door::Axle::_02::RightService::IVSS_SetIsOpenR2_Event*> (this));
// Unregister the data link signalss
// Unregister the data link signals
if (m_SignalFrontLeftDoorIsOpen)
m_SignalFrontLeftDoorIsOpen.Reset();
if (m_SignalFrontRightDoorIsOpen)
@@ -338,10 +338,10 @@ bool CConsole::PrepareDataConsumersForStandAlone()
}
// Registrate for the vehicle device & basic service of the front left door. Front left door mzust exist, the others are optional
auto vehicleDevice = sdv::core::GetObject("Vehicle.Body.Door.Axle._01.Left_Device").GetInterface<vss::Vehicle::Body::Door::Axle::_01::LeftDevice::IVSS_IsOpen>();
auto vehicleDevice = sdv::core::GetObject("Vehicle.Body.Door.Axle._01.Left_Device").GetInterface<vss::Vehicle::Body::Door::Axle::_01::LeftDevice::IVSS_ReadIsOpen>();
if (!vehicleDevice)
{
SDV_LOG_ERROR("Could not get interface 'LeftDevice::IVSS_IsOpen': [CConsole]");
SDV_LOG_ERROR("Could not get interface 'LeftDevice::IVSS_ReadIsOpen': [CConsole]");
return false;
}
vehicleDevice->RegisterIsOpenEvent(dynamic_cast<vss::Vehicle::Body::Door::Axle::_01::LeftDevice::IVSS_WriteIsOpen_Event*> (this));

View File

@@ -9,7 +9,7 @@
********************************************************************************/
#include "../door_app/include/door_application.h"
#include "../door_app/include/signal_names.h"
#include "../generated/vss_files/signal_identifier.h"
#ifdef _WIN32
#include <conio.h> // Needed for _kbhit

View File

@@ -9,7 +9,7 @@
********************************************************************************/
#include "../door_app/include/door_extern_application.h"
#include "../door_app/include/signal_names.h"
#include "../generated/vss_files/signal_identifier.h"
#ifdef _WIN32
#include <conio.h> // Needed for _kbhit

View File

@@ -19,7 +19,7 @@
#include <support/app_control.h>
#include <support/component_impl.h>
#include <support/timer.h>
#include "signal_names.h"
#include "../../generated/vss_files/signal_identifier.h"
#include <fcntl.h>
#include <atomic>
@@ -202,7 +202,7 @@ private:
* @param[in] sPos The location to print the value at.
* @param[in] rssName Reference to the value.
* @param[in] tValue The value.
* @param[in] rssStatus Status, becuse we have signals of type bool
* @param[in] rssStatus Status, because we have signals of type bool
*/
template <typename TValue>
void PrintValue(SConsolePos sPos, const std::string& rssName, TValue tValue, const std::string& rssStatus);
@@ -219,7 +219,6 @@ private:
bool m_bThreadStarted = false; ///< Set when initialized.
std::atomic_bool m_bRunning = false; ///< When set, the application is running.
bool m_isExternalApp = false; ///< True when we have an external application
mutable std::mutex m_mPrintToConsole; ///< Mutex to print complete message
std::thread m_threadReadTxSignals; ///< Simulation datalink thread.
@@ -269,7 +268,7 @@ inline void CConsole::PrintValue(SConsolePos sPos, const std::string& rssName, T
" " << std::fixed << std::setprecision(2) << tValue << " " << rssUnits <<
std::string(nEndNameLen - std::min(endName.size(), static_cast<size_t>(nEndNameLen - 1)) - 1, ' ');
std::lock_guard<std::mutex> lock(m_mPrintToConsole);
std::lock_guard<std::mutex> lock(m_mtxPrintToConsole);
SetCursorPos(sPos);
std::cout << sstreamValueText.str();
}

View File

@@ -64,7 +64,7 @@ private:
bool IsSDVFrameworkEnvironmentSet();
/**
* @brief Loac config file and register vehicle device and basic service.
* @brief Load config file and register vehicle device and basic service.
* @remarks It is expected that each config file has the complete door:
* vehicle device & basic service for input and output
* @param[in] inputMsg message string to be printed on console in case of success and failure

View File

@@ -21,12 +21,11 @@ public:
/**
* @brief Start and initialize the application control and load vehicle devices and
* basic services depending on the numerb of doors
* basic services depending on the number of doors
* @return Return true on success otherwise false
*/
bool Initialize();
/**
* @brief Run loop as long as user input does not exit
* Allow user to open/close each door.

View File

@@ -1,33 +0,0 @@
/********************************************************************************
* 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
********************************************************************************/
/**
* namespace for the signal names
* in case /interfaces/signal_identifier.h
* exists, use the file, otherwise define the namespace
*/
#ifndef SIGNAL_NAMES_H
#define SIGNAL_NAMES_H
namespace doors
{
// Data Dispatch Service signal names to dbc variable names C-type RX/TX vss name space
static std::string dsLeftDoorIsOpen01 = "CAN_Input_L1.Door01LeftIsOpen"; ///< bool RX Vehicle.Body.Door.Axle._01.Left
static std::string dsRightDoorIsOpen01 = "CAN_Input_R1.Door01RightIsOpen"; ///< bool RX Vehicle.Body.Door.Axle._01.Right
static std::string dsLeftDoorIsOpen02 = "CAN_Input_L2.Door02LeftIsOpen"; ///< bool RX Vehicle.Body.Door.Axle._02.Left
static std::string dsRightDoorIsOpen02 = "CAN_Input_R2.Door02RightIsOpen"; ///< bool RX Vehicle.Body.Door.Axle._02.Right
static std::string dsLeftLatch01 = "CAN_Output.LockDoor01Left"; ///< bool TX Vehicle.Body.Door.Axle._01.Left
static std::string dsRightLatch01 = "CAN_Output.LockDoor01Right"; ///< bool TX Vehicle.Body.Door.Axle._01.Right
static std::string dsLeftLatch02 = "CAN_Output.LockDoor02Left"; ///< bool TX Vehicle.Body.Door.Axle._02.Left
static std::string dsRightLatch02 = "CAN_Output.LockDoor02Right"; ///< bool TX Vehicle.Body.Door.Axle._02.Right
} // doors
#endif // SIGNAL_NAMES_H

View File

@@ -30,7 +30,7 @@ include_directories(${SDV_FRAMEWORK_DEV_INCLUDE})
# services are generated during the configuration phase of CMake. This is necessary, since CMakeFiles.txt files are generated and
# they have to be available during the configuration phase of CMake to be taken into the build process. Requisite for the code
# generation during the configuration time of CMake is the availability of the tools to do the generation. Hence the tools cannot be
# created during the build process, which is executed after the configuraiton process.
# created during the build process, which is executed after the configuration process.
# Execute sdv_vss_util to create IDL files for devices and basic services.
message("Create interface code for devices and basic services of open trunk example.")
@@ -46,7 +46,7 @@ execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/v
# We need proxies and stubs for basic services to give access to the interfaces for complex services ans applications: --ps_lib_namedemo_proxystub
# We need proxies and stubs for basic services to give access to the interfaces for complex services and applications: --ps_lib_namedemo_proxystub
message("Compiling vss_vehiclebodytrunk_bs_tx.idl")
execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclebodytrunk_bs_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_nametrunk_proxystub)
message("Compiling vss_vehiclespeed_bs_rx.idl")
@@ -78,7 +78,7 @@ add_subdirectory(generated/can_dl)
# REMARK: Proxy/stub and vehicle device and basic service code was generated during the configuration phase of CMake. Following
# below are the build steps to build the components that were generated.
message("Include: trunk proxy/stub for vehicle devices and basic services")
message("Include: trunk proxy/stub for basic services")
include_directories(${CMAKE_CURRENT_LIST_DIR}/generated/vss_files)
add_subdirectory(generated/vss_files/ps)
@@ -116,7 +116,6 @@ add_executable(open_trunk_example
open_trunk_app/trunk_application.h
open_trunk_app/console.cpp
open_trunk_app/console.h
open_trunk_app/signal_names.h
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")

View File

@@ -34,8 +34,8 @@ const CConsole::SConsolePos g_sComment3{ 24, 1 };
const CConsole::SConsolePos g_sComment4{ 25, 1 };
const CConsole::SConsolePos g_sComment5{ 26, 1 };
const CConsole::SConsolePos g_sSeparator5{ 28, 1 };
const CConsole::SConsolePos g_sComplexServcie1{ 30, 1 };
const CConsole::SConsolePos g_sComplexServcie2{ 31, 1 };
const CConsole::SConsolePos g_sComplexService1{ 30, 1 };
const CConsole::SConsolePos g_sComplexService2{ 31, 1 };
const CConsole::SConsolePos g_sSeparator6{ 33, 1 };
const CConsole::SConsolePos g_sControlDescription{ 35, 1 };
const CConsole::SConsolePos g_sCursor{ 36, 1 };
@@ -111,12 +111,12 @@ void CConsole::PrintHeader(uint32_t uiInstance)
PrintText(g_sSeparator4, "============================================================================");
PrintText(g_sComment1, "The complex service which checks the speed of the vehicle can be seen");
PrintText(g_sComment2, "as an ASIL A/B component and will block the call from QM.");
PrintText(g_sComment3, "The extern apllication can be seen as a QM function.");
PrintText(g_sComment3, "The extern application can be seen as a QM function.");
PrintText(g_sComment4, "If this example would run in a mixed critical environment the connection");
PrintText(g_sComment5, "from QM to basic service interface would be forbidden.");
PrintText(g_sSeparator5, "============================================================================");
PrintText(g_sComplexServcie1, "Basic Service Interface not available.");
PrintText(g_sComplexServcie2, "Complex Service Interface not available.");
PrintText(g_sComplexService1, "Basic Service Interface not available.");
PrintText(g_sComplexService2, "Complex Service Interface not available.");
PrintText(g_sSeparator6, "----------------------------------------------------------------------------");
PrintText(g_sControlDescription, "Press 'X' to quit; 'C' to clear screen, '1' or '2' to open trunk ");
}
@@ -130,7 +130,7 @@ bool CConsole::PrepareDataConsumers()
PrintValue(g_sDataLinkSpeed, "Vehicle Speed RX", m_SpeedDL, "m/s");
// Registrate for the vehicle device & basic service of the speed.
auto vehicleDevice = sdv::core::GetObject("Vehicle.Speed_Device").GetInterface<vss::Vehicle::SpeedDevice::IVSS_Speed>();
auto vehicleDevice = sdv::core::GetObject("Vehicle.Speed_Device").GetInterface<vss::Vehicle::SpeedDevice::IVSS_ReadSpeed>();
if (vehicleDevice)
{
PrintValue(g_sVehicleDeviceSpeed, "Vehicle Speed RX", m_SpeedVD, "m/s");
@@ -147,15 +147,15 @@ bool CConsole::PrepareDataConsumers()
// Request the basic service for opening the trunk.
m_pTrunkSvc = sdv::core::GetObject("Vehicle.Body.Trunk_Service").GetInterface<vss::Vehicle::Body::TrunkService::IVSS_SetOpen>();
if (m_pTrunkSvc)
PrintText(g_sComplexServcie1, "Basic Service available");
PrintText(g_sComplexService1, "Basic Service available");
else
PrintText(g_sComplexServcie1, "Basic Service NOT available");
PrintText(g_sComplexService1, "Basic Service NOT available");
m_pTrunkComplexService = sdv::core::GetObject("Open Trunk Service").GetInterface<ITrunkKitService>();
if (m_pTrunkComplexService)
PrintText(g_sComplexServcie2, "Complex Service available");
PrintText(g_sComplexService2, "Complex Service available");
else
PrintText(g_sComplexServcie2, "Complex Service NOT available");
PrintText(g_sComplexService2, "Complex Service NOT available");
return true;
}
@@ -165,7 +165,7 @@ void CConsole::ResetSignals()
// Set the cursor position at the end
SetCursorPos(g_sCursor);
auto vehicleDevice = sdv::core::GetObject("Vehicle.Speed_Device").GetInterface<vss::Vehicle::SpeedDevice::IVSS_Speed>();
auto vehicleDevice = sdv::core::GetObject("Vehicle.Speed_Device").GetInterface<vss::Vehicle::SpeedDevice::IVSS_ReadSpeed>();
if (vehicleDevice)
vehicleDevice->UnregisterSpeedEvent(dynamic_cast<vss::Vehicle::SpeedDevice::IVSS_WriteSpeed_Event*> (this));
@@ -173,7 +173,7 @@ void CConsole::ResetSignals()
if (basicService)
basicService->UnregisterOnSignalChangeOfVehicleSpeed(dynamic_cast<vss::Vehicle::SpeedService::IVSS_SetSpeed_Event*> (this));
// Unregister the data link signalss
// Unregister the data link signals
if (m_SignalSpeed)
m_SignalSpeed.Reset();
}
@@ -291,25 +291,25 @@ void CConsole::RunUntilBreak()
{
case 'c':
case 'C':
PrintText(g_sComplexServcie1, " ");
PrintText(g_sComplexServcie2, " ");
PrintText(g_sComplexService1, " ");
PrintText(g_sComplexService2, " ");
break;
case '1':
if (m_pTrunkSvc)
{
if (m_pTrunkSvc->SetOpen(true))
PrintText(g_sComplexServcie1, "Open trunk via basic service - will not be available in Mixed-Critical mode!");
PrintText(g_sComplexService1, "Open trunk via basic service - will not be available in Mixed-Critical mode!");
else
PrintText(g_sComplexServcie1, "Open trunk via basic service failed.");
PrintText(g_sComplexService1, "Open trunk via basic service failed.");
}
break;
case '2':
if (m_pTrunkComplexService)
{
if (m_pTrunkComplexService->PopTrunk())
PrintText(g_sComplexServcie2, "Safety open trunk via complex service.");
PrintText(g_sComplexService2, "Safety open trunk via complex service.");
else
PrintText(g_sComplexServcie2, "Safety open trunk via complex service failed, car is moving");
PrintText(g_sComplexService2, "Safety open trunk via complex service failed, car is moving");
}
break;
case 'x':

View File

@@ -18,7 +18,8 @@
#include <support/app_control.h>
#include <support/component_impl.h>
#include <support/timer.h>
#include "signal_names.h"
#include "../generated/vss_files/signal_identifier.h"
#include <fcntl.h>
#include "../generated/vss_files/vss_vehiclebodytrunk_bs_tx.h"
#include "../generated/vss_files/vss_vehiclespeed_bs_rx.h"
@@ -139,7 +140,7 @@ private:
* @param[in] sPos The location to print the value at.
* @param[in] rssName Reference to the value.
* @param[in] tValue The value.
* @param[in] rssStatus Status, becuse we have signals of type bool
* @param[in] rssStatus Status, because we have signals of type bool
*/
template <typename TValue>
void PrintValue(SConsolePos sPos, const std::string& rssName, TValue tValue, const std::string& rssStatus);
@@ -154,7 +155,6 @@ private:
mutable std::mutex m_mtxPrintToConsole; ///< Mutex to print complete message
bool m_bRunning = false; ///< When set, the application is running.
mutable std::mutex m_mPrintToConsole; ///< Mutex to print complete message
sdv::core::CSignal m_SignalSpeed; ///< Speed
float m_SpeedDL = 0.0; ///< Speed Data Link
@@ -186,7 +186,7 @@ inline void CConsole::PrintValue(SConsolePos sPos, const std::string& rssName, T
std::string(nValueNameLen - std::min(rssName.size(), static_cast<size_t>(nValueNameLen - 1)) - 1, '.') <<
" " << std::fixed << std::setprecision(2) << tValue << " " << rssUnits << " ";
std::lock_guard<std::mutex> lock(m_mPrintToConsole);
std::lock_guard<std::mutex> lock(m_mtxPrintToConsole);
SetCursorPos(sPos);
std::cout << sstreamValueText.str();
}

View File

@@ -1,35 +0,0 @@
/********************************************************************************
* 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
********************************************************************************/
/**
* namespace for the signal names
* in case /interfaces/signal_identifier.h
* exists, use the file, otherwise define the namespace
*/
#ifndef SIGNAL_NAMES_H
#define SIGNAL_NAMES_H
#ifdef __has_include
#if __has_include("../interfaces/signal_identifier.h")
#include "../interfaces/signal_identifier.h"
#else
namespace trunk
{
// Data Dispatch Service signal names to dbc variable names C-type RX/TX vss name space
static std::string dsVehicleSpeed = "CAN_Input.Speed"; ///< bool RX Vehicle.Speed
static std::string dsTrunk = "CAN_Output.OpenTrunk"; ///< bool TX Vehicle.Body.Trunk
} // trunk
#endif
#endif
#endif // SIGNAL_NAMES_H

View File

@@ -9,7 +9,7 @@
********************************************************************************/
#include "trunk_application.h"
#include "signal_names.h"
#include "../generated/vss_files/signal_identifier.h"
#ifdef _WIN32
#include <conio.h> // Needed for _kbhit

View File

@@ -11,7 +11,6 @@
#include <string>
#include <support/app_control.h>
#include <support/signal_support.h>
#include "vss_vehiclespeed_bs_rx.h"
/**
@@ -47,7 +46,7 @@ private:
bool IsSDVFrameworkEnvironmentSet();
/**
* @brief Loac config file and register vehicle device and basic service.
* @brief Load config file and register vehicle device and basic service.
* @param[in] inputMsg message string to be printed on console in case of success and failure
* @param[in] configFileName config toml file name
* @return Return true on success otherwise false

View File

@@ -30,7 +30,7 @@ include_directories(${SDV_FRAMEWORK_DEV_INCLUDE})
# services are generated during the configuration phase of CMake. This is necessary, since CMakeFiles.txt files are generated and
# they have to be available during the configuration phase of CMake to be taken into the build process. Requisite for the code
# generation during the configuration time of CMake is the availability of the tools to do the generation. Hence the tools cannot be
# created during the build process, which is executed after the configuraiton process.
# created during the build process, which is executed after the configuration process.
# Execute sdv_vss_util to create IDL files for devices and basic services.
message("Create interface code for devices and basic services of demo example.")
@@ -46,7 +46,7 @@ execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/v
message("Compiling vss_vehiclespeed_vd_rx.idl")
execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclespeed_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps)
# We need proxies and stubs for basic services to give access to the interfaces for complex services ans applications: --ps_lib_namedemo_proxystub
# We need proxies and stubs for basic services to give access to the interfaces for complex services and applications: --ps_lib_namedemo_proxystub
message("Compiling vss_vehiclechassisrearaxlerowwheel_bs_tx.idl")
execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclechassisrearaxlerowwheel_bs_tx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_namedemo_proxystub)
message("Compiling vss_vehiclechassissteeringwheelangle_bs_rx.idl")
@@ -81,7 +81,7 @@ add_subdirectory(generated/can_dl)
# REMARK: Proxy/stub and vehicle device and basic service code was generated during the configuration phase of CMake. Following
# below are the build steps to build the components that were generated.
message("Include: demo proxy/stub for vehicle devices and basic services")
message("Include: demo proxy/stub for basic services")
include_directories(${CMAKE_CURRENT_LIST_DIR}/generated/vss_files)
add_subdirectory(generated/vss_files/ps)
@@ -120,7 +120,6 @@ set_target_properties(demo_complex_service PROPERTIES SUFFIX ".sdv")
# Define the executable
add_executable(system_extern_example
example_app/system_extern_example.cpp
example_app/signal_names.h
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
@@ -145,7 +144,6 @@ add_executable(system_demo_example
example_app/control.cpp
example_app/console.h
example_app/console.cpp
example_app/signal_names.h
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")

View File

@@ -253,7 +253,7 @@ void CConsole::RunUntilBreak()
// Set the cursor position at the end
SetCursorPos(g_sCursor);
// Unregister the data link signalss
// Unregister the data link signals
if (m_signalSteeringWheel) m_signalSteeringWheel.Reset();
if (m_signalSpeed) m_signalSpeed.Reset();
if (m_signalRearAxleAngle) m_signalRearAxleAngle.Reset();

View File

@@ -18,7 +18,7 @@
#include <support/app_control.h>
#include <support/component_impl.h>
#include <support/timer.h>
#include "signal_names.h"
#include "../generated/vss_files/signal_identifier.h"
#ifdef __unix__
#include <termios.h> // Needed for tcgetattr and fcntl

View File

@@ -29,7 +29,7 @@
#endif
#endif
#include "signal_names.h"
#include "../generated/vss_files/signal_identifier.h"
/**
* @brief Utility handler for example demos

View File

@@ -1,35 +0,0 @@
/********************************************************************************
* 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
********************************************************************************/
/**
* namespace for the signal names
* in case /generated/vss_files/signal_identifier.h
* exists, use the file, otherwise define the namespace
*/
#ifndef SIGNAL_NAMES_H
#define SIGNAL_NAMES_H
#ifdef __has_include
#if __has_include("../generated/vss_files/signal_identifier.h")
#include "../generated/vss_files/signal_identifier.h"
#else
namespace demo
{
static std::string dsWheelAngle = "CAN_Input.SteeringWheel"; ///< float RX Vehicle.Chassis.SteeringWheel.Angle
static std::string dsVehicleSpeed = "CAN_Input.Speed"; ///< float RX Vehicle.Speed
static std::string dsAxleAngle = "CAN_Output.RearAngle"; ///< float TX Vehicle.Chassis.RearAxle.Row.Wheel
static std::string dsLiveCounter = "CAN_Output.IsActiveCounter"; ///< uint8_t TX Vehicle.Software.Application.IsActiveCounter
} // demo
#endif
#endif
#endif // ! defined SIGNAL_NAMES_H

View File

@@ -0,0 +1,123 @@
#*******************************************************************************
# 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
#*******************************************************************************
project(SystemDemoExample)
# Use new policy for project version settings and default warning level
cmake_policy(SET CMP0048 NEW) # requires CMake 3.14
cmake_policy(SET CMP0092 NEW) # requires CMake 3.15
set(CMAKE_CXX_STANDARD 17)
# Libary symbols are hidden by default
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
# Include directory to the core framework
include_directories(${SDV_FRAMEWORK_DEV_INCLUDE})
######################################################################################################################################################################
# preparation
######################################################################################################################################################################
# REMARK: The code generation for the proxy/stub, interface definitions and serialization, the vehicle devices and the basic
# services are generated during the configuration phase of CMake. This is necessary, since CMakeFiles.txt files are generated and
# they have to be available during the configuration phase of CMake to be taken into the build process. Requisite for the code
# generation during the configuration time of CMake is the availability of the tools to do the generation. Hence the tools cannot be
# created during the build process, which is executed after the configuration process.
# Execute sdv_vss_util to create IDL files for devices and basic services.
# REMARK: We need 2 cvs definition files because we want to create 2 identical components.
# Both need to have the same input signal as well as the same output interface. Output must be vehicle speed in km/h.
# One of the component has to convert the value from m/s to km/h, the other one can pass the value directly.
message("Create interface code for devices and basic services of vehicle abstraction example.")
execute_process(COMMAND "${SDV_VSS_UTIL}" "${PROJECT_SOURCE_DIR}/vehicle_abstraction_example.csv" "-O${PROJECT_SOURCE_DIR}/generated/" --prefixabstraction --version1.0.0.1 --enable_components)
execute_process(COMMAND "${SDV_VSS_UTIL}" "${PROJECT_SOURCE_DIR}/vehicle_abstraction_example_ms.csv" "-O${PROJECT_SOURCE_DIR}/generated2/" --prefixabstraction --version1.0.0.1 --enable_components)
# Execute the IDL compiler for the VSS interfaces to digest interface code. Compile with --no_ps as we do not need proxies and stubs as we do not like to expose these interfaces for complex services or applications
message("Compiling vss_vehiclespeed_vd_rx.idl")
execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclespeed_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --no_ps)
message("Compiling vss_vehiclespeed_vd_rx.idl")
execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated2/vss_files/vss_vehiclespeed_vd_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated2/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated2/vss_files/ --no_ps)
# We need proxies and stubs for basic services to give access to the interfaces for complex services and applications: --ps_lib_namedemo_proxystub
message("Compiling vss_vehiclespeed_bs_rx.idl")
execute_process(COMMAND "${SDV_IDL_COMPILER}" "${PROJECT_SOURCE_DIR}/generated/vss_files/vss_vehiclespeed_bs_rx.idl" "-O${PROJECT_SOURCE_DIR}/generated/vss_files/" "-I${SDV_FRAMEWORK_DEV_INCLUDE}" -Igenerated/vss_files/ --ps_lib_nameabstraction_proxystub)
# Execute sdv_dbc_util to create data link code & FMU code.
message("Create data link for vehicle abstraction example")
execute_process(COMMAND ${SDV_DBC_UTIL} "${PROJECT_SOURCE_DIR}/vehicle_abstraction_example.dbc" "-O${PROJECT_SOURCE_DIR}/generated/" --nodesabstraction --version1.0.0.1 --dl_lib_namecan_dl_abstraction)
######################################################################################################################################################################
# data link component
######################################################################################################################################################################
# REMARK: CAN data link code was generated during the configuration phase of CMake. Following below is the build step to build the
# component that was generated.
message("Include: example component can_dl_abstraction")
add_subdirectory(generated/can_dl)
#######################################################################################################################################################################
## vehicle devices and basic services
#######################################################################################################################################################################
# REMARK: Proxy/stub and vehicle device and basic service code was generated during the configuration phase of CMake. Following
# below are the build steps to build the components that were generated.
message("Include: vehicle abstraction proxy/stub for basic services")
include_directories(${CMAKE_CURRENT_LIST_DIR}/generated/vss_files)
add_subdirectory(generated/vss_files/ps)
# REMARK: We create 2 'vd_*' components with identical input and output interface. Therefore we needed 2 csv definition files for their creation.
# The difference is: one passes the input value to output, the other one needs to convert the value from m/s to km/h to match the unit.
add_subdirectory(generated/vss_files/vd_vehiclespeedkmh)
add_subdirectory(generated2/vss_files/vd_vehiclespeedms)
# REMARK: Because the output interface of the 'vd_*' components is identical (km/h), we need only one bs* component which expects to get a value in km/h.
add_subdirectory(generated/vss_files/bs_vehiclespeed)
######################################################################################################################################################################
# vehicle abstraction application
######################################################################################################################################################################
# Define the executable
add_executable(vehicle_abstraction_example
vehicle_abstraction_app/vehicle_abstraction_example.cpp
vehicle_abstraction_app/vehicle_abstraction_application.cpp
vehicle_abstraction_app/vehicle_abstraction_application.h
vehicle_abstraction_app/console.cpp
vehicle_abstraction_app/console.h
)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if (WIN32)
target_link_libraries(vehicle_abstraction_example Ws2_32 Winmm Rpcrt4.lib)
else()
target_link_libraries(vehicle_abstraction_example ${CMAKE_DL_LIBS} rt ${CMAKE_THREAD_LIBS_INIT})
endif()
else()
target_link_libraries(vehicle_abstraction_example Rpcrt4.lib)
endif()
# Copy the config files
file (COPY ${PROJECT_SOURCE_DIR}/config/can_com_simulation_vehicle_abstraction_ms.toml DESTINATION ${CMAKE_BINARY_DIR}/bin/config)
file (COPY ${PROJECT_SOURCE_DIR}/config/can_com_simulation_vehicle_abstraction_kmh.toml DESTINATION ${CMAKE_BINARY_DIR}/bin/config)
file (COPY ${PROJECT_SOURCE_DIR}/config/data_dispatch_vehicle_abstraction.toml DESTINATION ${CMAKE_BINARY_DIR}/bin/config)
file (COPY ${PROJECT_SOURCE_DIR}/config/data_link_vehicle_abstraction.toml DESTINATION ${CMAKE_BINARY_DIR}/bin/config)
file (COPY ${PROJECT_SOURCE_DIR}/config/task_timer_vehicle.toml DESTINATION ${CMAKE_BINARY_DIR}/bin/config)
file (COPY ${PROJECT_SOURCE_DIR}/config/vehicle_abstraction_device_kmh.toml DESTINATION ${CMAKE_BINARY_DIR}/bin/config)
file (COPY ${PROJECT_SOURCE_DIR}/config/vehicle_abstraction_device_ms.toml DESTINATION ${CMAKE_BINARY_DIR}/bin/config)
file (COPY ${PROJECT_SOURCE_DIR}/config/vehicle_abstraction_basic_service.toml DESTINATION ${CMAKE_BINARY_DIR}/bin/config)
# Copy the ASC files used by can_com_sim.sdv which includes the CAN messages. One file includes vehicle speed in m/s, the other file in km/h.
file (COPY ${PROJECT_SOURCE_DIR}/vehicle_abstraction_example_receiver_ms.asc DESTINATION ${CMAKE_BINARY_DIR}/bin)
file (COPY ${PROJECT_SOURCE_DIR}/vehicle_abstraction_example_receiver_kmh.asc DESTINATION ${CMAKE_BINARY_DIR}/bin)

View File

@@ -0,0 +1,9 @@
[Configuration]
Version = 100
[[Component]]
Path = "can_com_sim.sdv"
Class = "CAN_Com_Sim"
[Component.Parameters]
Source="vehicle_abstraction_example_receiver_kmh.asc"
Target="vehicle_abstraction_example_writer_kmh.asc"

View File

@@ -0,0 +1,9 @@
[Configuration]
Version = 100
[[Component]]
Path = "can_com_sim.sdv"
Class = "CAN_Com_Sim"
[Component.Parameters]
Source="vehicle_abstraction_example_receiver_ms.asc"
Target="vehicle_abstraction_example_writer_ms.asc"

View File

@@ -0,0 +1,8 @@
[Configuration]
Version = 100
[[Component]]
Path = "data_dispatch_service.sdv"
Class = "DataDispatchService"

View File

@@ -0,0 +1,6 @@
[Configuration]
Version = 100
[[Component]]
Path = "can_dl_abstraction.sdv"
Class = "CAN_data_link"

View File

@@ -0,0 +1,6 @@
[Configuration]
Version = 100
[[Component]]
Path = "task_timer.sdv"
Class = "TaskTimerService"

View File

@@ -0,0 +1,12 @@
[Configuration]
Version = 100
[[Component]]
Path = "abstraction_bs_vehiclespeed_rx.sdv"
Class = "Vehicle.Speed_Service"

View File

@@ -0,0 +1,6 @@
[Configuration]
Version = 100
[[Component]]
Path = "abstraction_vd_vehiclespeedkmh_rx.sdv"
Class = "Vehicle.Speed_Device"

View File

@@ -0,0 +1,8 @@
[Configuration]
Version = 100
[[Component]]
Path = "abstraction_vd_vehiclespeedms_rx.sdv"
Class = "Vehicle.Speed_Device"

View File

@@ -0,0 +1,316 @@
/********************************************************************************
* 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
********************************************************************************/
#include "console.h"
#ifdef _WIN32
#include <conio.h> // Needed for _kbhit
#else
#include <fcntl.h>
#endif
const CConsole::SConsolePos g_sTitle{ 1, 1 };
const CConsole::SConsolePos g_sSubTitle1{ 2, 1 };
const CConsole::SConsolePos g_sSubTitle2{3, 1};
const CConsole::SConsolePos g_sSeparator1{ 5, 1 };
const CConsole::SConsolePos g_sDataUnit{ 7, 1 };
const CConsole::SConsolePos g_sDataLinkSpeed{ 8, 1 };
const CConsole::SConsolePos g_sSeparator2{ 10, 1 };
const CConsole::SConsolePos g_sDeviceServiceSpeed{ 12, 1 };
const CConsole::SConsolePos g_sSeparator3{ 14, 1 };
const CConsole::SConsolePos g_sBasicServiceSpeed{ 16, 1 };
const CConsole::SConsolePos g_sSeparator4{ 18, 1 };
const CConsole::SConsolePos g_sComment1{ 20, 1 };
const CConsole::SConsolePos g_sComment2{ 21, 1 };
const CConsole::SConsolePos g_sComment3{ 23, 1 };
const CConsole::SConsolePos g_sComment4{ 24, 1 };
const CConsole::SConsolePos g_sComment5{ 25, 1 };
const CConsole::SConsolePos g_sComment6{ 27, 1 };
const CConsole::SConsolePos g_sComment7{ 28, 1 };
const CConsole::SConsolePos g_sSeparator5{ 30, 1 };
const CConsole::SConsolePos g_sControlDescription{ 32, 1 };
const CConsole::SConsolePos g_sCursor{ 33, 1 };
CConsole::CConsole()
{
#ifdef _WIN32
// Enable ANSI escape codes
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hStdOut != INVALID_HANDLE_VALUE && GetConsoleMode(hStdOut, &m_dwConsoleOutMode))
SetConsoleMode(hStdOut, m_dwConsoleOutMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
if (hStdIn != INVALID_HANDLE_VALUE && GetConsoleMode(hStdIn, &m_dwConsoleInMode))
SetConsoleMode(hStdIn, m_dwConsoleInMode & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT));
#elif defined __unix__
// Disable echo
tcgetattr(STDIN_FILENO, &m_sTermAttr);
struct termios sTermAttrTemp = m_sTermAttr;
sTermAttrTemp.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &sTermAttrTemp);
m_iFileStatus = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, m_iFileStatus | O_NONBLOCK);
#else
#error The OS is not supported!
#endif
}
CConsole::~CConsole()
{
SetCursorPos(g_sCursor);
#ifdef _WIN32
// Return to the stored console mode
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hStdOut != INVALID_HANDLE_VALUE)
SetConsoleMode(hStdOut, m_dwConsoleOutMode);
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
if (hStdIn != INVALID_HANDLE_VALUE)
SetConsoleMode(hStdIn, m_dwConsoleInMode);
#elif defined __unix__
// Return the previous file status flags.
fcntl(STDIN_FILENO, F_SETFL, m_iFileStatus);
// Return to previous terminal state
tcsetattr(STDIN_FILENO, TCSANOW, &m_sTermAttr);
#endif
}
void CConsole::PrintHeader()
{
// Clear the screen...
std::cout << "\x1b[2J";
// Print the titles
PrintText(g_sTitle, "Vehicle Abstraction: ");
PrintText(g_sSubTitle1, "This example demonstrates that the vehicle function implementation is independent of the vehicle itself.");
PrintText(g_sSubTitle2, "CAN bus vehicle 1 => km/h, CAN bus vehicle 2 => m/s.");
PrintText(g_sSeparator1, "====================================================================================================================");
PrintText(g_sDataUnit, m_DataUnit);
PrintText(g_sDataLinkSpeed, "Data Link not available.");
PrintText(g_sSeparator2, "--------------------------------------------------------------------------------------------------------------------");
PrintText(g_sDeviceServiceSpeed, "Platform Abstraction Interface not available.");
PrintText(g_sSeparator3, "--------------------------------------------------------------------------------------------------------------------");
PrintText(g_sBasicServiceSpeed, "Basic Service Interface not available.");
PrintText(g_sSeparator4, "====================================================================================================================");
PrintText(g_sComment1, "The dispatch service displays the value that is written by the CAN message read from the ASC file.");
PrintText(g_sComment2, "The speed should be increased by steps of 10 km/h.");
PrintText(g_sComment3, "The 'Platform Abstraction' component is responsible for the vehicle abstraction and converts the value if necessary.");
PrintText(g_sComment4, "The example contains 2 components for 'Platform Abstraction' with identical output interfaces.");
PrintText(g_sComment5, "Depending on the input (m/s or km/h) the correct 'Platform Abstraction' component must be loaded.");
PrintText(g_sComment6, "Therefore, only one speed sensor component is required, which always receives the speed in km/h.");
PrintText(g_sComment7, "The vehicle function does not require any logic that depends on the specific vehicle.");
PrintText(g_sSeparator5, "====================================================================================================================");
PrintText(g_sControlDescription, "Press 'X' to quit;");
}
bool CConsole::PrepareDataConsumers()
{
sdv::core::CDispatchService dispatch;
m_SignalSpeed = dispatch.Subscribe(abstraction::dsVehicleSpeed, [&](sdv::any_t value) { CallbackSpeed(value); });
if (m_SignalSpeed)
{
auto unit = m_Unit;
unit.append(" [ data dispatch service - input from CAN bus ] ");
PrintValue(g_sDataLinkSpeed, "Vehicle Speed RX", m_SpeedDataLink, unit);
}
auto deviceServiceSpeed = sdv::core::GetObject("Vehicle.Speed_Device").GetInterface<vss::Vehicle::SpeedDevice::IVSS_ReadSpeed>();
if (deviceServiceSpeed)
{
PrintValue(g_sDeviceServiceSpeed, "Vehicle Speed RX", m_PlatformSpeed, "km/h [ Output of Platform Abstraction, not accessible by application ] ");
deviceServiceSpeed->RegisterSpeedEvent(dynamic_cast<vss::Vehicle::SpeedDevice::IVSS_WriteSpeed_Event*> (this));
}
auto basicServiceSpeed = sdv::core::GetObject("Vehicle.Speed_Service").GetInterface<vss::Vehicle::SpeedService::IVSS_GetSpeed>();
if (basicServiceSpeed)
{
PrintValue(g_sBasicServiceSpeed, "Vehicle Speed RX", m_BasicSpeed, "km/h [ Output of Speed Sensor Service, accessible by application ] ");
basicServiceSpeed->RegisterOnSignalChangeOfVehicleSpeed(dynamic_cast<vss::Vehicle::SpeedService::IVSS_SetSpeed_Event*> (this));
}
return true;
}
void CConsole::WriteSpeed( float value)
{
if (m_PlatformSpeed != value)
{
m_PlatformSpeed = value;
PrintValue(g_sDeviceServiceSpeed, "Vehicle Speed RX", m_PlatformSpeed, "km/h [ Output of Platform Abstraction, not accessible by application ] ");
}
}
void CConsole::SetSpeed( float value)
{
if (m_BasicSpeed != value)
{
m_BasicSpeed= value;
PrintValue(g_sBasicServiceSpeed, "Vehicle Speed RX", m_BasicSpeed, "km/h [ Output of Speed Sensor Service, accessible by application ] ");
}
}
void CConsole::ResetSignals()
{
// Set the cursor position at the end
SetCursorPos(g_sCursor);
auto deviceServiceSpeed = sdv::core::GetObject("Vehicle.Speed_Devicee").GetInterface<vss::Vehicle::SpeedDevice::IVSS_ReadSpeed>();
if (deviceServiceSpeed)
{
deviceServiceSpeed->UnregisterSpeedEvent(dynamic_cast<vss::Vehicle::SpeedDevice::IVSS_WriteSpeed_Event*> (this));
}
auto basicServiceSpeed = sdv::core::GetObject("Vehicle.Speed_Service").GetInterface<vss::Vehicle::SpeedService::IVSS_GetSpeed>();
if (basicServiceSpeed)
{
basicServiceSpeed->UnregisterOnSignalChangeOfVehicleSpeed(dynamic_cast<vss::Vehicle::SpeedService::IVSS_SetSpeed_Event*> (this));
}
if (m_SignalSpeed)
m_SignalSpeed.Reset();
}
void CConsole::CallbackSpeed(sdv::any_t value)
{
if (m_SpeedDataLink != value.get<float>())
{
m_SpeedDataLink = value.get<float>();
auto unit = m_Unit;
unit.append(" [ data dispatch service - input from CAN bus ] ");
PrintValue(g_sDataLinkSpeed, "Vehicle Speed RX", m_SpeedDataLink, unit);
}
}
CConsole::SConsolePos CConsole::GetCursorPos() const
{
SConsolePos sPos{};
std::cout << "\033[6n";
char buff[128];
int indx = 0;
for(;;) {
int cc = std::cin.get();
buff[indx] = (char)cc;
indx++;
if(cc == 'R') {
buff[indx + 1] = '\0';
break;
}
}
int iRow = 0, iCol = 0;
sscanf(buff, "\x1b[%d;%dR", &iRow, &iCol);
sPos.uiRow = static_cast<uint32_t>(iRow);
sPos.uiCol = static_cast<uint32_t>(iCol);
fseek(stdin, 0, SEEK_END);
return sPos;
}
void CConsole::SetCursorPos(SConsolePos sPos)
{
std::cout << "\033[" << sPos.uiRow << ";" << sPos.uiCol << "H";
}
void CConsole::PrintText(SConsolePos sPos, const std::string& rssText)
{
auto text = rssText;
while (text.length() < 76)
text.append(" ");
std::lock_guard<std::mutex> lock(m_mtxPrintToConsole);
SetCursorPos(sPos);
std::cout << text;
}
bool CConsole::KeyHit()
{
#ifdef _WIN32
return _kbhit();
#elif __unix__
int ch = getchar();
if (ch != EOF) {
ungetc(ch, stdin);
return true;
}
return false;
#endif
}
char CConsole::GetChar()
{
#ifdef _WIN32
return static_cast<char>(_getch());
#else
return getchar();
#endif
}
void CConsole::RunUntilBreak()
{
bool bRunning = true;
while (bRunning)
{
// Check for a key
if (!KeyHit())
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
// Get a keyboard value (if there is any).
char c = GetChar();
switch (c)
{
case 'x':
case 'X':
bRunning = false;
break;
default:
break;
}
}
}
bool CConsole::SelectInputDataUnits()
{
// Clear the screen and goto top...
std::cout << "\x1b[2J\033[0;0H";
std::cout << "Click '1' to select km/h or '2' to select m/s";
bool bIsKmh = false;
char c = '0';
while(c != '1' && c != '2')
{
c = GetChar();
switch (c)
{
case '1':
m_DataUnit = "[ --- Input data in km/h --- ]";
m_Unit = "km/h";
bIsKmh = true;
break;
case '2':
m_DataUnit = "[ --- Input data in m/s --- ]";
m_Unit = "m/s";
bIsKmh = false;
break;
default:
break;
}
}
return bIsKmh;
}

View File

@@ -0,0 +1,200 @@
/********************************************************************************
* 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
********************************************************************************/
#ifndef CONSOLE_OUTPUT_H
#define CONSOLE_OUTPUT_H
#include <iostream>
#include <string>
#include <functional>
#include <support/signal_support.h>
#include <support/app_control.h>
#include <support/component_impl.h>
#include <support/timer.h>
#include "../generated/vss_files/signal_identifier.h"
#include <fcntl.h>
#include "../generated/vss_files/vss_vehiclespeed_vd_rx.h"
#include "../generated/vss_files/vss_vehiclespeed_bs_rx.h"
#ifdef __unix__
#include <termios.h> // Needed for tcgetattr and fcntl
#include <unistd.h>
#endif
/**
* @brief Console operation class.
* @details This class retrieves RX data from the data dispatch service, platform abstraction & basic service of speed on event change.
* Furthermore, it shows TX value by polling the RX signals.
*/
class CConsole : public vss::Vehicle::SpeedDevice::IVSS_WriteSpeed_Event
, public vss::Vehicle::SpeedService::IVSS_SetSpeed_Event
{
public:
/**
* @brief Screen position structure
*/
struct SConsolePos
{
uint32_t uiRow; ///< Row position (starts at 1)
uint32_t uiCol; ///< Column position (starts at 1)
};
/**
* @brief Constructor
*/
CConsole();
/**
* @brief Destructor
*/
~CConsole();
/**
* @brief Print the header.
*/
void PrintHeader();
/**
* @brief Prepare the data consumers..
* @return Returns whether the preparation of the data consumers was successful or not.
*/
bool PrepareDataConsumers();
/**
* @brief Run loop as long as user input does not exit
*/
void RunUntilBreak();
/**
* @brief Let the user decide which unit the input data should have
* @return Return true if inout data should be km/h, otherwise false
*/
bool SelectInputDataUnits();
/**
* @brief Write vehicleSpeed signal
* @param[in] value vehicleSpeed
*/
void WriteSpeed( float value) override;
/**
* @brief Set vehicleSpeed signal
* @param[in] value vehicleSpeed
*/
void SetSpeed( float value) override;
/**
* @brief For gracefully shutdown all signals need to be reset.
*/
void ResetSignals();
private:
/**
* @brief Callback function for speed.
* @param[in] value The value of the signal to update.
*/
void CallbackSpeed(sdv::any_t value);
/**
* @brief Key hit check. Windows uses the _kbhit function; POSIX emulates this.
* @return Returns whether a key has been pressed.
*/
bool KeyHit();
/**
* @brief Get the character from the keyboard buffer if pressed.
* @return Returns the character from the keyboard buffer.
*/
char GetChar();
/**
* @brief Get the cursor position of the console.
* @return The cursor position.
*/
SConsolePos GetCursorPos() const;
/**
* @brief Set the current cursor position for the console.
* @param[in] sPos Console position to place the current cursor at.
*/
void SetCursorPos(SConsolePos sPos);
/**
* @brief Print text at a specific location.
* @param[in] sPos The location to print text at.
* @param[in] rssText Reference to the text to print.
*/
void PrintText(SConsolePos sPos, const std::string& rssText);
/**
* @brief Print a value string at a specific location.
* @tparam TValue Type of value.
* @param[in] sPos The location to print the value at.
* @param[in] rssName Reference to the value.
* @param[in] tValue The value.
* @param[in] rssStatus Status, because we have signals of type bool
*/
template <typename TValue>
void PrintValue(SConsolePos sPos, const std::string& rssName, TValue tValue, const std::string& rssStatus);
/**
* @brief Align string between name and value.
* @param[in] message Reference to the message to align.
* @param[in] desiredLength The desired length or 0 when no length is specified.
* @return The aligned string.
*/
std::string AlignString(const std::string& message, uint32_t desiredLength = 0);
mutable std::mutex m_mtxPrintToConsole; ///< Mutex to print complete message
bool m_bRunning = false; ///< When set, the application is running.
std::string m_DataUnit = "[ --- Input data in m/s --- ]";
std::string m_Unit = "m/s";
sdv::core::CSignal m_SignalSpeed; ///< Signal to subscribe to the speed signal in dispatch service
float m_SpeedDataLink = 0.0; ///< Data Link value, either km/h or m/s
float m_PlatformSpeed = 0.0; ///< Generalized Speed
float m_BasicSpeed = 0.0; ///< Generalized Speed
#ifdef _WIN32
DWORD m_dwConsoleOutMode = 0u; ///< The console mode before switching on ANSI support.
DWORD m_dwConsoleInMode = 0u; ///< The console mode before switching on ANSI support.
#elif defined __unix__
struct termios m_sTermAttr{}; ///< The terminal attributes before disabling echo.
int m_iFileStatus = 0; ///< The file status flags for STDIN.
#else
#error The OS is not supported!
#endif
};
template <typename TValue>
inline void CConsole::PrintValue(SConsolePos sPos, const std::string& rssName, TValue tValue, const std::string& rssUnits)
{
const size_t nValueNameLen = 26;
std::stringstream sstreamValueText;
sstreamValueText << rssName <<
std::string(nValueNameLen - std::min(rssName.size(), static_cast<size_t>(nValueNameLen - 1)) - 1, '.') <<
" " << std::fixed << std::setprecision(2) << tValue << " " << rssUnits << " ";
std::lock_guard<std::mutex> lock(m_mtxPrintToConsole);
SetCursorPos(sPos);
std::cout << sstreamValueText.str();
}
template <>
inline void CConsole::PrintValue<bool>(SConsolePos sPos, const std::string& rssName, bool bValue, const std::string& rssStatus)
{;
PrintValue(sPos, rssName, bValue ? "" : "", rssStatus);
}
#endif // !define CONSOLE_OUTPUT_H

View File

@@ -0,0 +1,103 @@
/********************************************************************************
* 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
********************************************************************************/
#include "vehicle_abstraction_application.h"
#include "../generated/vss_files/signal_identifier.h"
#ifdef _WIN32
#include <conio.h> // Needed for _kbhit
#else
#include <fcntl.h>
#endif
bool CAbstractionControl::LoadConfigFile(const std::string& inputMsg, const std::string& configFileName)
{
std::string msg = inputMsg;
if (m_appcontrol.LoadConfig(configFileName) == sdv::core::EConfigProcessResult::successful)
{
msg.append("ok\n");
std::cout << msg.c_str();
return true;
}
msg.append("FAILED.\n");
std::cout << msg.c_str();
return false;
}
bool CAbstractionControl::IsSDVFrameworkEnvironmentSet()
{
const char* envVariable = std::getenv("SDV_FRAMEWORK_RUNTIME");
if (envVariable)
{
return true;
}
return false;
}
bool CAbstractionControl::Initialize(bool bSimulate)
{
if (m_bInitialized)
return true;
m_bSimulate = bSimulate;
if (!IsSDVFrameworkEnvironmentSet())
{
// if SDV_FRAMEWORK_RUNTIME environment variable is not set we need to set the Framework Runtime directory
m_appcontrol.SetFrameworkRuntimeDirectory("../../bin");
}
if (!m_appcontrol.Startup(""))
return false;
m_appcontrol.SetConfigMode();
bool bResult = LoadConfigFile("Load dispatch service: ", "data_dispatch_vehicle_abstraction.toml");
bResult &= LoadConfigFile("Load task_timer_vehicle: ", "task_timer_vehicle.toml");
if (bSimulate)
{
bResult &= LoadConfigFile("Load can_com_simulation_vehicle_abstraction_kmh: ", "can_com_simulation_vehicle_abstraction_kmh.toml");
bResult &= LoadConfigFile("Load data_link_vehicle_abstraction: ", "data_link_vehicle_abstraction.toml");
bResult &= LoadConfigFile("Load vehicle_abstraction_kmh: ", "vehicle_abstraction_device_kmh.toml");
}
else
{
bResult &= LoadConfigFile("Load can_com_simulation_vehicle_abstraction_ms: ", "can_com_simulation_vehicle_abstraction_ms.toml");
bResult &= LoadConfigFile("Load data_link_vehicle_abstraction: ", "data_link_vehicle_abstraction.toml");
bResult &= LoadConfigFile("Load vehicle_abstraction_ms: ", "vehicle_abstraction_device_ms.toml");
}
bResult &= LoadConfigFile("Load vehicle_abstraction_basic_service: ", "vehicle_abstraction_basic_service.toml");
if (!bResult)
{
SDV_LOG_ERROR("One or more configurations could not be loaded. Cannot continue.");
return false;
}
m_bInitialized = true;
return true;
}
void CAbstractionControl::Shutdown()
{
if (!m_bInitialized)
m_appcontrol.Shutdown();
m_bInitialized = false;
}
void CAbstractionControl::SetRunningMode()
{
m_appcontrol.SetRunningMode();
}

View File

@@ -0,0 +1,60 @@
/********************************************************************************
* 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
********************************************************************************/
#include <string>
#include <support/app_control.h>
#include <support/signal_support.h>
/**
* @brief Application Class of the vehicle abstraction example
*/
class CAbstractionControl
{
public:
/**
* @brief Start and initialize the application control and load
* platform abstraction components and basic services
* @param[in] bSimulate If signals should be simulated or CAN input signals should be used
* @return Return true on success otherwise false
*/
bool Initialize(bool bSimulate);
/**
* @brief After initialization/configuration the system mode needs to be set to running mode
*/
void SetRunningMode();
/**
* @brief Shutdown the system.
*/
void Shutdown();
private:
/**
* @brief check if SDV_FRAMEWORK_RUNTIME environment variable exists
* @return Return true if environment variable is found otherwise false
*/
bool IsSDVFrameworkEnvironmentSet();
/**
* @brief Load config file and register vehicle device and basic service.
* @param[in] inputMsg message string to be printed on console in case of success and failure
* @param[in] configFileName config toml file name
* @return Return true on success otherwise false
*/
bool LoadConfigFile(const std::string& inputMsg, const std::string& configFileName);
sdv::app::CAppControl m_appcontrol; ///< App-control of SDV V-API.
bool m_bInitialized = false; ///< Set when initialized.
bool m_bSimulate = false;
};

View File

@@ -0,0 +1,47 @@
/********************************************************************************
* 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
********************************************************************************/
#include <iostream>
#include <cstdlib> // for std::strtol
#include <fstream>
#include <filesystem>
#include <sstream>
#include <ostream>
#include "vehicle_abstraction_application.h"
#include "console.h"
#if defined(_WIN32) && defined(_UNICODE)
extern "C" int wmain([[maybe_unused]] int argc, [[maybe_unused]] wchar_t* argv[])
{
#else
extern "C" int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
#endif
CConsole visual_obj;
CAbstractionControl appobj;
if (!appobj.Initialize(visual_obj.SelectInputDataUnits()))
{
std::cout << "ERROR: Failed to initialize application control." << std::endl;
return 0;
}
visual_obj.PrintHeader();
visual_obj.PrepareDataConsumers();
appobj.SetRunningMode();
visual_obj.RunUntilBreak();
visual_obj.ResetSignals();
appobj.Shutdown();
return 0;
}

View File

@@ -0,0 +1,4 @@
;Class name;Function name;Signal name;vss;Signal direction;type;DBC CAN name includes CAN message name
;;;;;;;
VD;VehicleSpeedKmh;Speed;vehicleSpeed;Vehicle.Speed;RX;float;CAN_Input.Speed_General
BS;VehicleSpeed;Speed;vehicleSpeed;Vehicle.Speed;RX;float;Vehicle.Speed
1 Class name Function name Signal name vss Signal direction type DBC CAN name includes CAN message name
2
3 VD VehicleSpeedKmh Speed vehicleSpeed Vehicle.Speed RX float CAN_Input.Speed_General
4 BS VehicleSpeed Speed vehicleSpeed Vehicle.Speed RX float Vehicle.Speed

View File

@@ -0,0 +1,115 @@
VERSION "PrivateCAN"
NS_ :
NS_DESC_
CM_
BA_DEF_
BA_
VAL_
CAT_DEF_
CAT_
FILTER
BA_DEF_DEF_
EV_DATA_
ENVVAR_DATA_
SGTYPE_
SGTYPE_VAL_
BA_DEF_SGTYPE_
BA_SGTYPE_
SIG_TYPE_REF_
VAL_TABLE_
SIG_GROUP_
SIG_VALTYPE_
SIGTYPE_VALTYPE_
BO_TX_BU_
BA_DEF_REL_
BA_REL_
BA_DEF_DEF_REL_
BU_SG_REL_
BU_EV_REL_
BU_BO_REL_
SG_MUL_VAL_
BS_:
BU_: abstraction
VAL_TABLE_ Fault_Codes 27 "UKWN" 26 "VEHSPDMAX_EXDD" 25 "STS_ALIVE" 24 "STEER_NOT_E2E_MODE" 23 "OTA_SPD" 22 "OTA_TIMER_DOWNLOAD_FAILED" 21 "OTA_MAX_TIME" 20 "CUBIXAD_STEERSTREQ_NOTACTV" 19 "CUBIXAD_DRVSTREQ_NOTACTV" 18 "SFTYDRV_INTV" 17 "LSDC_ALIVE" 16 "CUBIXAD_ALIVE" 15 "IBC_MAB_NO_PRIO" 14 "IBC_NOT_RDY" 13 "IBC_ALIVE" 12 "LSDC_GEAR" 11 "LSDC_SPD" 10 "LSDC_ACCL" 9 "IBC_NOT_MAB_MOD" 8 "GOLDBOX_ALIVE" 7 "CUBIXAD_GEAR" 6 "CUBIXAD_SPD_TESTTRACK" 5 "DRVREQCHG" 4 "RDY_TIMER" 3 "SFTY_CDN_FAILED" 2 "ACTVNCHK_SPD" 1 "ACTVNCHK_TIMR" 0 "NONE" ;
VAL_TABLE_ TestMapID 6 "E_TESTMAPID_UNDEFINED" 5 "E_TESTMAPID_TEST_DRIVE" 4 "E_TESTMAPID_AD_AREA" 3 "E_TESTMAPID_STUTT_ARENA" 2 "E_TESTMAPID_ZF_LASTMILE" 1 "E_TESTMAPID_ZF_TESTTRACK_2" 0 "E_TESTMAPID_NONE" ;
VAL_TABLE_ CtrlReqStates 7 "CtrlSts3b_RESERVED_4" 6 "CtrlSts3b_RESERVED_3" 5 "CtrlSts3b_RESERVED_2" 4 "CtrlSts3b_RESERVED_1" 3 "CtrlSts3b_ERROR" 2 "CtrlSts3b_CONTROL_REQUESTED" 1 "CtrlSts3b_CONTROL_NOT_REQUESTED" 0 "CtrlSts3b_INIT" ;
VAL_TABLE_ SteerActrReSts 7 "Diag" 6 "Inactive" 5 "Ramping" 4 "Yellow" 3 "Red" 2 "Normal" 1 "Pending" 0 "Initialisation" ;
VAL_TABLE_ SwtPark1 1 "SwtParkActv" 0 "SwtParkNotActv" ;
VAL_TABLE_ PE_State 2 "ERROR" 1 "INIT" 0 "NO_ERROR" ;
VAL_TABLE_ SSM_Req 7 "HMS_TAKEOVER" 6 "RESERVED" 5 "RELESE_VIA_RAMP" 4 "DRIVEOFF" 3 "HOLD_STANDBY" 2 "PARK" 1 "HOLD" 0 "NO_REQUEST" ;
VAL_TABLE_ IBC_StandStillMode 12 "SSM_ERROR" 11 "SSM_INIT" 10 "SSM_DRIVEOFF_STANDBY_ACTIVE" 9 "SSM_HOLD_STANDBY_ACTIVE" 8 "SSM_HILL_SLIPPOFF_DETECTED" 7 "SSM_RELEASE_REQ_FROM_DRIVER" 6 "SSM_RELEASE_REQ_ACTIVE" 5 "SSM_DRIVEOFF_ACTIVE" 4 "SSM_PARK_RETAINED_ACTIVE" 3 "SSM_PARK_ACTIVE" 2 "SSM_PARK_REQUESTED" 1 "SSM_HOLD_ACTIVE" 0 "SSM_NO_ACTIVE_FUNCTION" ;
VAL_TABLE_ AppTgtStDrv 3 "ACTIVE" 2 "READY" 1 "RESERVED" 0 "NOT_ACTIVE" ;
VAL_TABLE_ IBC_Status 4 "IBC_MAB_ERR_COMM" 3 "IBC_MAB_NO_PRIO" 2 "IBC_IN_MAB_MODE" 1 "IBC_READY" 0 "IBC_NOT_READY_FAILED" ;
VAL_TABLE_ GearLvrIndcn 7 "GearLvrIndcn2_Undefd" 6 "GearLvrIndcn2_Resd2" 5 "GearLvrIndcn2_Resd1" 4 "GearLvrIndcn2_ManModeIndcn" 3 "GearLvrIndcn2_DrvIndcn" 2 "GearLvrIndcn2_NeutIndcn" 1 "GearLvrIndcn2_RvsIndcn" 0 "GearLvrIndcn2_ParkIndcn" ;
VAL_TABLE_ LvlgAdjReq 7 "LvlgAdjReq_Resd2" 6 "LvlgAdjReq_Resd1" 5 "LvlgAdjReq_Ll2" 4 "LvlgAdjReq_Ll1" 3 "LvlgAdjReq_Nrh" 2 "LvlgAdjReq_Hl1" 1 "LvlgAdjReq_Hl2" 0 "LvlgAdjReq_Ukwn" ;
VAL_TABLE_ DrvModReq 15 "Err" 14 "Rock" 13 "Mud" 12 "Sand" 11 "Snow" 10 "Power" 9 "Hybrid" 8 "Pure_EV" 7 "Race" 6 "Adaptive" 5 "Offroad_CrossTerrain" 4 "Individual" 3 "Dynamic_Sport" 2 "Comfort_Normal" 1 "ECO" 0 "Undefd" ;
VAL_TABLE_ MAB_Info_Message 4 "DRV_GEARLVR_TO_P" 3 "DRV_P_TO_D" 2 "LSDC_DI_NOT_PSBL" 1 "LSDC_ENA_NOT_POSSIBLE" 0 "NONE" ;
VAL_TABLE_ MAB_OvrdTool_Sts 11 "HACKATHON" 10 "OTA" 9 "INIT" 8 "FINSHD" 7 "FLT" 6 "CUBIX_AD" 5 "SAVE_THE_SPOILER" 4 "LSDC" 3 "RDY" 2 "ACTVN_CHK" 1 "NO_MANIPULATION" 0 "NONE" ;
VAL_TABLE_ HMI_Drvr_Req 9 "FCT_DEACTVN_REQ" 8 "FCT_ACTVN_OTA_CFMD" 7 "FCT_ACTVN_OTA_REQ" 6 "FCT_ACTVN_SAVETHESPOILER_CFMD" 5 "FCT_ACTVN_SAVETHESPOILER_REQ" 4 "FCT_ACTVN_LSDC_CFMD" 3 "FCT_ACTVN_LSDC_REQ" 2 "FCT_ACTVN_CUBIXAD_CFMD" 1 "FCT_ACTVN_CUBIXAD_REQ" 0 "FCT_ACTVN_NONE" ;
VAL_TABLE_ Info_Message 4 "DRV_GEARLVR_TO_P" 3 "DRV_P_TO_D" 2 "LSDC_DI_NOT_PSBL" 1 "LSDC_ENA_NOT_POSSIBLE" 0 "NONE" ;
VAL_TABLE_ HMI_Fct_Req 8 "FCT_DEACTVN_REQ" 7 "FCT_ACTVN_OTA_REQ" 6 "FCT_ACTVN_SAVETHESPOILER_CFMD" 5 "FCT_ACTVN_SAVETHESPOILER_REQ" 4 "FCT_ACTVN_LSDC_CFMD" 3 "FCT_ACTVN_LSDC_REQ" 2 "FCT_ACTVN_AI4MTN_CFMD" 1 "FCT_ACTVN_AI4MTN_REQ" 0 "FCT_ACTVN_NONE" ;
VAL_TABLE_ SOVD_states 2 "SOVD_SHOWCASE_ACTIVE" 1 "SOVD_SHOWCASE_DEACTIVE" 0 "SOVD_NONE" ;
VAL_TABLE_ OTA_states 7 "OTA_DOWNLOAD_FAILED" 6 "OTA_INSTALL_FAILED" 5 "OTA_INSTALL_FINISHED" 4 "OTA_INSTALL_START" 3 "OTA_DOWNLOAD_START" 2 "OTA_SCHEDULED" 1 "OTA_STANDBY" 0 "OTA_NONE" ;
BO_ 1 CAN_Input: 8 Vector__XXX
SG_ Speed_General : 7|15@0+ (0.00391,0) [0|128.11897] "km/h" abstraction
CM_ SG_ 1 Speed_General "Vehicle speed used in a example to demonstrate vehicel abstraction. One data is in m/s, the otherdata is km/h";
BA_DEF_ "Baudrate" INT 1000 1000000;
BA_DEF_ "BusType" STRING ;
BA_DEF_ "DBName" STRING ;
BA_DEF_ "ProtocolType" STRING ;
BA_DEF_ BU_ "NmAsrNode" ENUM "No","Yes";
BA_DEF_ BU_ "NmAsrNodeIdentifier" INT 0 255;
BA_DEF_ BO_ "GenMsgCycleTime" INT 0 65536;
BA_DEF_ BO_ "GenMsgCycleTimeFast" FLOAT 0 300000;
BA_DEF_ BO_ "GenMsgDelayTime" INT 0 65536;
BA_DEF_ BO_ "GenMsgNrOfRepetition" INT 0 100000;
BA_DEF_ BO_ "GenMsgSendType" ENUM "cyclic","spontaneous","not-used","not-used","not-used","cyclicAndSpontaneous","not-used","cyclicIfActive","NoMsgSendType";
BA_DEF_ BO_ "GenMsgStartDelayTime" INT 0 65536;
BA_DEF_ SG_ "GenSigSendType" ENUM "Cyclic","OnWrite","OnWriteWithRepetition","OnChange","OnChangeWithRepetition","IfActive","IfActiveWithRepetition","NoSigSendType";
BA_DEF_ SG_ "GenSigStartValue" HEX 0 80000000;
BA_DEF_ BO_ "GenMsgILSupport" ENUM "No","Yes";
BA_DEF_ BO_ "NmAsrMessage" ENUM "No","Yes";
BA_DEF_ "NmAsrBaseAddress" HEX 0 536870911;
BA_DEF_ "NmAsrMessageCount" INT 0 255;
BA_DEF_ BU_ "NodeLayerModules" STRING ;
BA_DEF_ BU_ "ILused" ENUM "No","Yes";
BA_DEF_ SG_ "GenSigFuncType" ENUM "NoFunction","n/a","n/a","n/a","n/a","n/a","n/a","n/a","n/a","n/a","n/a","n/a","n/a","n/a","n/a","CHK","CNTR","n/a","n/a","n/a","CNTR_AR_01","CRC_AR_01_BOTH","CRC_AR_01_ALT","CRC_AR_01_LOW","CRC_AR_01_NIBBLE","CNTR_AR_04","CRC_AR_04A","CNTR_AR_05","CRC_AR_05";
BA_DEF_ SG_ "GenSigDataID" STRING ;
BA_DEF_ SG_ "SigGroup" STRING ;
BA_DEF_DEF_ "Baudrate" 1000;
BA_DEF_DEF_ "BusType" "";
BA_DEF_DEF_ "DBName" "";
BA_DEF_DEF_ "ProtocolType" "";
BA_DEF_DEF_ "NmAsrNode" "No";
BA_DEF_DEF_ "NmAsrNodeIdentifier" 0;
BA_DEF_DEF_ "GenMsgCycleTime" 0;
BA_DEF_DEF_ "GenMsgCycleTimeFast" 0;
BA_DEF_DEF_ "GenMsgDelayTime" 0;
BA_DEF_DEF_ "GenMsgNrOfRepetition" 0;
BA_DEF_DEF_ "GenMsgSendType" "NoMsgSendType";
BA_DEF_DEF_ "GenMsgStartDelayTime" 0;
BA_DEF_DEF_ "GenSigSendType" "NoSigSendType";
BA_DEF_DEF_ "GenSigStartValue" 0;
BA_DEF_DEF_ "GenMsgILSupport" "Yes";
BA_DEF_DEF_ "NmAsrMessage" "No";
BA_DEF_DEF_ "NmAsrBaseAddress" 1280;
BA_DEF_DEF_ "NmAsrMessageCount" 64;
BA_DEF_DEF_ "NodeLayerModules" "CANoeILNLSPA.dll";
BA_DEF_DEF_ "ILused" "Yes";
BA_DEF_DEF_ "GenSigFuncType" "NoFunction";
BA_DEF_DEF_ "GenSigDataID" "";
BA_DEF_DEF_ "SigGroup" "";
BA_ "ProtocolType" "CAN";
BA_ "BusType" "CAN";
BA_ "Baudrate" 500000;
BA_ "DBName" "PrivateCAN";

View File

@@ -0,0 +1,3 @@
;Class name;Function name;Signal name;vss;Signal direction;type;DBC CAN name includes CAN message name;
;;;;;;;;
VD;VehicleSpeedMs;Speed;vehicleSpeed;Vehicle.Speed;RX;float;CAN_Input.Speed_General;"float vehicleSpeed = value.get<float>() * 3.6f;"
1 Class name Function name Signal name vss Signal direction type DBC CAN name includes CAN message name
2
3 VD VehicleSpeedMs Speed vehicleSpeed Vehicle.Speed RX float CAN_Input.Speed_General float vehicleSpeed = value.get<float>() * 3.6f;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -72,9 +72,9 @@ module sdv
};
/**
* @brief Connection status enumeration
* @brief Connection state enumeration
*/
enum EConnectStatus : uint32
enum EConnectState : uint32
{
uninitialized = 0, ///< Initialization via IConnect pending/required
initializing = 1, ///< Initiation channel access.
@@ -96,8 +96,8 @@ module sdv
{
/**
* @brief Establish a connection and start sending/receiving messages.
* @param[in] pReceiver Callback interface for receiving data and connect status.
* @return Returns 'true' when a channel connection could be initiated. Use GetStatus or IConnectEventCallback to
* @param[in] pReceiver Callback interface for receiving data and connect state.
* @return Returns 'true' when a channel connection could be initiated. Use GetConnectState or IConnectEventCallback to
* check the connection state.
*/
boolean AsyncConnect(in IInterfaceAccess pReceiver);
@@ -116,33 +116,32 @@ module sdv
void CancelWait();
/**
* @brief Disconnect from a connection. This will set the connect status to disconnected and release the interface
* used for the status events.
* @brief Disconnect from a connection. This will set the connect state to disconnected and release the interface
* used for the state events.
*/
void Disconnect();
/**
* @brief Register event callback interface.
* @details Register a connection status event callback interface. The exposed interface must be of type
* @details Register a connection state event callback interface. The exposed interface must be of type
* IConnectEventCallback. The registration will exist until a call to the unregister function with the returned cookie
* or until the connection is terminated.
* @param[in] pEventCallback Pointer to the object exposing the IConnectEventCallback interface.
* @return The cookie assigned to the registration or 0 when the registration wasn't successful.
*/
uint64 RegisterStatusEventCallback(in IInterfaceAccess pEventCallback);
uint64 RegisterStateEventCallback(in IInterfaceAccess pEventCallback);
/**
* @brief Unregister the status event callback with the returned cookie from the registration.
* @brief Unregister the state event callback with the returned cookie from the registration.
* @param[in] uiCookie The cookie returned by a previous call to the registration function.
*/
void UnregisterStatusEventCallback(in uint64 uiCookie);
void UnregisterStateEventCallback(in uint64 uiCookie);
/**
* @brief Gets the current status of the IPC whether it is initialized/connected/disconnected or having connection
* error.
* @return Returns retrieved status of the IPC.
* @brief Get the current state of the IPC conection.
* @return Returns connection state.
*/
EConnectStatus GetStatus() const;
EConnectState GetConnectState() const;
};
/**
@@ -151,10 +150,10 @@ module sdv
interface IConnectEventCallback
{
/**
* @brief Set the current status.
* @param[in] eConnectStatus The connection status.
* @brief Set the current connect state.
* @param[in] eConnectState The connection state.
*/
void SetStatus(in EConnectStatus eConnectStatus);
void SetConnectState(in EConnectState eConnectState);
};
/**

View File

@@ -900,7 +900,7 @@ namespace sdv
// Lock the parameter map - do not allow any more changes
LockParamMap();
// Set status
// Set state
m_eObjectState = EObjectState::shutdown_in_progress;
// Inform derived class
@@ -1001,7 +1001,7 @@ namespace sdv
END_SDV_INTERFACE_MAP()
private:
std::atomic<EObjectState> m_eObjectState = EObjectState::initialization_pending; ///< Object status
std::atomic<EObjectState> m_eObjectState = EObjectState::initialization_pending; ///< Object state
std::string m_ssObjectConfig; ///< Copy of the configuration TOML.
};

View File

@@ -32,17 +32,17 @@ struct SConnectEventCallbackWrapper : sdv::IInterfaceAccess, sdv::ipc::IConnectE
END_SDV_INTERFACE_MAP()
/**
* @brief Set the current status. Overload of sdv::ipc::IConnectEventCallback::SetStatus.
* @param[in] eConnectStatus The connection status.
* @brief Set the current connect state. Overload of sdv::ipc::IConnectEventCallback::SetConnectState.
* @param[in] eConnectState The connection state.
*/
virtual void SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus) override
virtual void SetConnectState(/*in*/ sdv::ipc::EConnectState eConnectState) override
{
switch (eConnectStatus)
switch (eConnectState)
{
case sdv::ipc::EConnectStatus::disconnected:
case sdv::ipc::EConnectStatus::disconnected_forced:
case sdv::ipc::EConnectStatus::connection_error:
case sdv::ipc::EConnectStatus::communication_error:
case sdv::ipc::EConnectState::disconnected:
case sdv::ipc::EConnectState::disconnected_forced:
case sdv::ipc::EConnectState::connection_error:
case sdv::ipc::EConnectState::communication_error:
{
auto pRequestShutdown = sdv::core::GetObject<sdv::app::IAppShutdownRequest>("AppControlService");
if (!pRequestShutdown) break;
@@ -237,13 +237,13 @@ extern "C" int main(int iArgc, const char* rgszArgv[])
return APP_CONTROL_INVALID_ISOLATION_CONFIG;
}
SConnectEventCallbackWrapper sConnectEventWrapper;
uint64_t uiCookie = pConnect->RegisterStatusEventCallback(&sConnectEventWrapper);
uint64_t uiCookie = pConnect->RegisterStateEventCallback(&sConnectEventWrapper);
// Connect to the core repository
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
if (!pConnectionControl)
{
pConnect->UnregisterStatusEventCallback(uiCookie);
pConnect->UnregisterStateEventCallback(uiCookie);
if (!bSilent)
std::cerr << "ERROR: " << COMMUNICATION_CONTROL_SERVICE_ACCESS_ERROR_MSG << std::endl;
return COMMUNICATION_CONTROL_SERVICE_ACCESS_ERROR;
@@ -252,7 +252,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[])
sdv::com::TConnectionID tConnection = pConnectionControl->AssignClientEndpoint(ptrChannelEndpoint, 3000, pCoreRepo);
if (!tConnection.uiControl || !pCoreRepo)
{
pConnect->UnregisterStatusEventCallback(uiCookie);
pConnect->UnregisterStateEventCallback(uiCookie);
if (!bSilent)
std::cerr << "ERROR: " << CONNECT_SDV_SERVER_ERROR_MSG << std::endl;
return CONNECT_SDV_SERVER_ERROR;
@@ -262,7 +262,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[])
sdv::core::ILinkCoreRepository* pLinkCoreRepo = sdv::core::GetObject<sdv::core::ILinkCoreRepository>("RepositoryService");
if (!pLinkCoreRepo)
{
pConnect->UnregisterStatusEventCallback(uiCookie);
pConnect->UnregisterStateEventCallback(uiCookie);
if (!bSilent)
std::cerr << "ERROR: " << LINK_REPO_SERVICE_ERROR_MSG << std::endl;
return LINK_REPO_SERVICE_ERROR;
@@ -281,7 +281,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[])
sdv::core::IRepositoryUtilityCreate* pCreateUtility = sdv::core::GetObject<sdv::core::IRepositoryUtilityCreate>("RepositoryService");
if (!pRepositoryInfo || !pRepositoryControl || !pObjectAccess || !pCreateUtility)
{
pConnect->UnregisterStatusEventCallback(uiCookie);
pConnect->UnregisterStateEventCallback(uiCookie);
if (!bSilent)
std::cerr << "ERROR: " << REPOSITORY_SERVICE_ACCESS_ERROR_MSG << std::endl;
return REPOSITORY_SERVICE_ACCESS_ERROR;
@@ -356,7 +356,7 @@ extern "C" int main(int iArgc, const char* rgszArgv[])
}
// Shutdwown
pConnect->UnregisterStatusEventCallback(uiCookie);
pConnect->UnregisterStateEventCallback(uiCookie);
parser.Clear();
pLinkCoreRepo->UnlinkCoreRepository();
appcontrol.Shutdown();

View File

@@ -314,10 +314,10 @@ std::string CVSSBSCodingRX::Code_BS_RXConstructor(const SSignalVDDefinition& sig
mapKeywords["signal_name"] = function.signalName;
return ReplaceKeywords(R"code(
auto %signal_name%Device = sdv::core::GetObject("%vd_vss_original%_Device").GetInterface<vss::%vd_vssWithColons%Device::IVSS_%vd_function_name%>();
auto %signal_name%Device = sdv::core::GetObject("%vd_vss_original%_Device").GetInterface<vss::%vd_vssWithColons%Device::IVSS_Read%vd_function_name%>();
if (!%signal_name%Device)
{
SDV_LOG_ERROR("Could not get interface 'IVSS_%vd_function_name%': [CBasicService%vd_class_name%]");
SDV_LOG_ERROR("Could not get interface 'IVSS_Read%vd_function_name%': [CBasicService%vd_class_name%]");
throw std::runtime_error("%vd_vss_original% mode device not found");
}
%signal_name%Device->Register%vd_function_name%Event(dynamic_cast<vss::%vd_vssWithColons%Device::IVSS_Write%vd_function_name%_Event*> (this));
@@ -337,7 +337,7 @@ std::string CVSSBSCodingRX::Code_BS_RXDestructor(const SSignalVDDefinition& sign
mapKeywords["vd_function_name"] = functionVD.functionName;
return ReplaceKeywords(R"code(
auto %vd_signal_name%Device = sdv::core::GetObject("%vd_vss_original%_Device").GetInterface<vss::%vd_vssWithColons%Device::IVSS_%vd_function_name%>();
auto %vd_signal_name%Device = sdv::core::GetObject("%vd_vss_original%_Device").GetInterface<vss::%vd_vssWithColons%Device::IVSS_Read%vd_function_name%>();
if (%vd_signal_name%Device)
{
%vd_signal_name%Device->Unregister%vd_function_name%Event(dynamic_cast<vss::%vd_vssWithColons%Device::IVSS_Write%vd_function_name%_Event*> (this));

View File

@@ -30,7 +30,7 @@ const char szRXVehicleDeviceHeaderTemplate[] = R"code(/**
#include "../signal_identifier.h"
/**
* @brief Vehicle device %vss_original%
* @brief Platform abstraction %vss_original%
*/
class CVehicleDevice%class_name%
: public sdv::CSdvObject
@@ -128,7 +128,7 @@ public:
BEGIN_SDV_INTERFACE_MAP()
%rx_bs_interface_entry_list% END_SDV_INTERFACE_MAP()
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::device)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::sensor)
DECLARE_OBJECT_CLASS_NAME("%vss_original%_Service")
/**

View File

@@ -30,7 +30,7 @@ const char szTXVehicleDeviceHeaderTemplate[] = R"code(/**
/**
* @brief Vehicle device %vss_shorten_no_dot%
* @brief Platform abstraction %vss_shorten_no_dot%
*/
class CVehicleDevice%class_name%
: public sdv::CSdvObject
@@ -124,7 +124,7 @@ public:
BEGIN_SDV_INTERFACE_MAP()
%tx_bs_interface_entry_list% END_SDV_INTERFACE_MAP()
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::device)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::actuator)
DECLARE_OBJECT_CLASS_NAME("%vss_original%_Service")
/**

View File

@@ -193,9 +193,9 @@ std::string CVSSVDCodingRX::Code_RXIDLDeviceInterface(const std::string& spaces,
%multiple_spaces%};
%multiple_spaces%/**
%multiple_spaces%* @brief IVSS_%function_name% abstract %prefix% interface
%multiple_spaces%* @brief IVSS_Read%function_name% abstract %prefix% interface
%multiple_spaces%*/
%multiple_spaces%interface IVSS_%function_name%
%multiple_spaces%interface IVSS_Read%function_name%
%multiple_spaces%{
%multiple_spaces% /**
%multiple_spaces% * @brief Register Write%function_name% event on signal change
@@ -276,7 +276,7 @@ std::string CVSSVDCodingRX::Code_VD_RXInterface(const std::string & functionName
mapKeywords["function_name"] = functionName;
mapKeywords["vss_shorten_with_colons"] = vssShortenWithColons;
return ReplaceKeywords(R"code( , public vss::%vss_shorten_with_colons%Device::IVSS_%function_name%
return ReplaceKeywords(R"code( , public vss::%vss_shorten_with_colons%Device::IVSS_Read%function_name%
)code", mapKeywords);
}
@@ -286,7 +286,7 @@ std::string CVSSVDCodingRX::Code_VD_RXInterfaceEntry(const std::string& function
mapKeywords["function_name"] = functionName;
mapKeywords["vssWithColons"] = vssWithColons;
return ReplaceKeywords(R"code( SDV_INTERFACE_ENTRY(vss::%vssWithColons%Device::IVSS_%function_name%)
return ReplaceKeywords(R"code( SDV_INTERFACE_ENTRY(vss::%vssWithColons%Device::IVSS_Read%function_name%)
)code", mapKeywords);
}
@@ -305,7 +305,7 @@ std::string CVSSVDCodingRX::Code_VD_RXReAndUnregisterEvent( const std::string& v
void Register%function_name%Event(vss::%vssWithColons%Device::IVSS_Write%function_name%_Event* event) override;
/**
* @brief Unregister IVSS_%function_name%_Event
* @brief Unregister IVSS_Read%function_name%_Event
* @param[in] event function
*/
void Unregister%function_name%Event(vss::%vssWithColons%Device::IVSS_Write%function_name%_Event* event) override;
@@ -339,7 +339,7 @@ std::string CVSSVDCodingRX::Code_VD_RXPrivateHeaderPart(const SFunctionVDDefinit
*/
void ExecuteAllCallBacksFor%start_with_uppercase%(sdv::any_t value);
sdv::core::CSignal m_%signal_name%Signal; ///< Signal of the vehicle device
sdv::core::CSignal m_%signal_name%Signal; ///< Signal of the platform abstraction
mutable std::mutex m_%signal_name%MutexCallbacks; ///< Mutex protecting m_%signal_name%Callbacks
std::set<vss::%vssWithColons%Device::IVSS_Write%function_name%_Event*> m_%signal_name%Callbacks; ///< collection of events to be called
)code", mapKeywords);

View File

@@ -212,8 +212,10 @@ add_subdirectory(task_timer)
add_subdirectory(ipc_com)
add_subdirectory(ipc_connect)
add_subdirectory(ipc_shared_mem)
add_subdirectory(uds_win_sockets)
add_subdirectory(uds_unix_sockets)
add_subdirectory(uds_unix_tunnel)
add_subdirectory(uds_win_sockets)
add_subdirectory(uds_win_tunnel)
add_subdirectory(process_control)
add_subdirectory(hardware_ident)
add_subdirectory(manifest_util)

View File

@@ -82,7 +82,7 @@ public:
private:
sdv::TInterfaceAccessPtr m_ptrObject; ///< Smart pointer to the object.
sdv::IObjectControl* m_pObjectControl = nullptr; ///< Pointer to the object control of the application
sdv::EObjectState m_eObjectState = sdv::EObjectState::initialization_pending; ///< Object status (in case there is no object control).
sdv::EObjectState m_eObjectState = sdv::EObjectState::initialization_pending; ///< Object state (in case there is no object control).
};
#endif // !defined ISOLATION_OBJECT_MONITOR_H

View File

@@ -55,7 +55,7 @@ CChannelConnector::~CChannelConnector()
sdv::ipc::IConnect* pConnection = m_ptrChannelEndpoint.GetInterface<sdv::ipc::IConnect>();
if (pConnection)
{
if (m_uiConnectionStatusCookie) pConnection->UnregisterStatusEventCallback(m_uiConnectionStatusCookie);
if (m_uiConnectStateCookie) pConnection->UnregisterStateEventCallback(m_uiConnectStateCookie);
pConnection->Disconnect();
}
@@ -78,8 +78,8 @@ bool CChannelConnector::ServerConnect(sdv::IInterfaceAccess* pObject, bool bAllo
// Establish connection...
sdv::ipc::IConnect* pConnection = m_ptrChannelEndpoint.GetInterface<sdv::ipc::IConnect>();
if (!pConnection) return false;
m_uiConnectionStatusCookie = pConnection->RegisterStatusEventCallback(this);
return m_uiConnectionStatusCookie != 0 && pConnection->AsyncConnect(this);
m_uiConnectStateCookie = pConnection->RegisterStateEventCallback(this);
return m_uiConnectStateCookie != 0 && pConnection->AsyncConnect(this);
}
sdv::IInterfaceAccess* CChannelConnector::ClientConnect(uint32_t uiTimeoutMs)
@@ -99,8 +99,8 @@ sdv::IInterfaceAccess* CChannelConnector::ClientConnect(uint32_t uiTimeoutMs)
// Establish connection...
sdv::ipc::IConnect* pConnection = m_ptrChannelEndpoint.GetInterface<sdv::ipc::IConnect>();
if (pConnection) m_uiConnectionStatusCookie = pConnection->RegisterStatusEventCallback(this);
if (!pConnection || m_uiConnectionStatusCookie == 0 || !pConnection->AsyncConnect(this) ||
if (pConnection) m_uiConnectStateCookie = pConnection->RegisterStateEventCallback(this);
if (!pConnection || m_uiConnectStateCookie == 0 || !pConnection->AsyncConnect(this) ||
!pConnection->WaitForConnection(uiTimeoutMs))
{
SDV_LOG_ERROR("Could not establish a connection!");
@@ -113,17 +113,17 @@ sdv::IInterfaceAccess* CChannelConnector::ClientConnect(uint32_t uiTimeoutMs)
bool CChannelConnector::IsConnected() const
{
return m_eConnectStatus == sdv::ipc::EConnectStatus::connected;
return m_eConnectState == sdv::ipc::EConnectState::connected;
}
void CChannelConnector::SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus)
void CChannelConnector::SetConnectState(/*in*/ sdv::ipc::EConnectState eConnectState)
{
auto eConnectStatusTemp = m_eConnectStatus;
m_eConnectStatus = eConnectStatus;
switch (m_eConnectStatus)
auto eConnectStateTemp = m_eConnectState;
m_eConnectState = eConnectState;
switch (m_eConnectState)
{
case sdv::ipc::EConnectStatus::disconnected:
case sdv::ipc::EConnectStatus::disconnected_forced:
case sdv::ipc::EConnectState::disconnected:
case sdv::ipc::EConnectState::disconnected_forced:
// Invalidate the proxy objects.
for (auto& rvtProxyObject : m_mapProxyObjects)
rvtProxyObject.second.reset();
@@ -131,7 +131,7 @@ void CChannelConnector::SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus
if (m_eEndpointType == EEndpointType::server)
{
// Report information (but only once)
if (sdv::app::ConsoleIsVerbose() && eConnectStatusTemp != sdv::ipc::EConnectStatus::disconnected)
if (sdv::app::ConsoleIsVerbose() && eConnectStateTemp != sdv::ipc::EConnectState::disconnected)
std::cout << "Client disconnected (ID#" << m_tConnectionID.uiIdent << ")" << std::endl;
// Remove the connection if reconnection is not enabled (normal case).
@@ -139,7 +139,7 @@ void CChannelConnector::SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus
m_rcontrol.RemoveConnection(m_tConnectionID);
}
break;
case sdv::ipc::EConnectStatus::connected:
case sdv::ipc::EConnectState::connected:
if (m_eEndpointType == EEndpointType::server)
{
// Report information

View File

@@ -71,10 +71,10 @@ public:
bool IsConnected() const;
/**
* @brief Set the current status. Overload of sdv::ipc::IConnectEventCallback::SetStatus.
* @param[in] eConnectStatus The connection status.
* @brief Set the current connect statue. Overload of sdv::ipc::IConnectEventCallback::SetConnectState.
* @param[in] eConnectState The connection state.
*/
virtual void SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus) override;
virtual void SetConnectState(/*in*/ sdv::ipc::EConnectState eConnectState) override;
/**
* @brief Callback to be called by the IPC connection when receiving a data packet. Overload of
@@ -139,14 +139,14 @@ private:
} eState = EState::initialized; ///< Data processing state.
sdv::sequence<sdv::pointer<uint8_t>> seqResult; ///< The result data.
std::mutex mtxWaitForResult; ///< Mutex to protect result processing.
std::condition_variable cvWaitForResult; ///< Condition variable to trigger result processing.
std::condition_variable cvWaitForResult; ///< Condition var to trigger result processing.
};
CCommunicationControl& m_rcontrol; ///< Reference to the communication control class.
sdv::TObjectPtr m_ptrChannelEndpoint; ///< Managed pointer to the channel endpoint.
uint64_t m_uiConnectionStatusCookie = 0; ///< Connection status cookie (received after registration).
uint64_t m_uiConnectStateCookie = 0; ///< Connection state cookie (received after registration).
std::shared_ptr<CMarshallObject> m_ptrInitialMarshallObject; ///< Initial marshall object used after a connect event.
sdv::ipc::EConnectStatus m_eConnectStatus = sdv::ipc::EConnectStatus::uninitialized; ///< Current connection status.
sdv::ipc::EConnectState m_eConnectState = sdv::ipc::EConnectState::uninitialized; ///< Current connection state.
bool m_bAllowReconnect = false; ///< When set, allow reconnection of the server.
EEndpointType m_eEndpointType = EEndpointType::client; ///< Endpoint type of this connector.
std::recursive_mutex m_mtxMarshallObjects; ///< Synchronize access to the marshall object vector.

View File

@@ -25,21 +25,21 @@ inline sdv::process::TProcessID GetProcessID()
return pProcessInfo ? pProcessInfo->GetProcessID() : 0;
}
inline std::string ConnectState(sdv::ipc::EConnectStatus eState)
inline std::string ConnectState2String(sdv::ipc::EConnectState eState)
{
switch (eState)
{
case sdv::ipc::EConnectStatus::uninitialized: return "uninitialized";
case sdv::ipc::EConnectStatus::initializing: return "initializing";
case sdv::ipc::EConnectStatus::initialized: return "initialized";
case sdv::ipc::EConnectStatus::connecting: return "connecting";
case sdv::ipc::EConnectStatus::negotiating: return "negotiating";
case sdv::ipc::EConnectStatus::connection_error: return "connection_error";
case sdv::ipc::EConnectStatus::connected: return "connected";
case sdv::ipc::EConnectStatus::communication_error: return "communication_error";
case sdv::ipc::EConnectStatus::disconnected: return "disconnected";
case sdv::ipc::EConnectStatus::disconnected_forced: return "disconnected_forced";
case sdv::ipc::EConnectStatus::terminating: return "terminating";
case sdv::ipc::EConnectState::uninitialized: return "uninitialized";
case sdv::ipc::EConnectState::initializing: return "initializing";
case sdv::ipc::EConnectState::initialized: return "initialized";
case sdv::ipc::EConnectState::connecting: return "connecting";
case sdv::ipc::EConnectState::negotiating: return "negotiating";
case sdv::ipc::EConnectState::connection_error: return "connection_error";
case sdv::ipc::EConnectState::connected: return "connected";
case sdv::ipc::EConnectState::communication_error: return "communication_error";
case sdv::ipc::EConnectState::disconnected: return "disconnected";
case sdv::ipc::EConnectState::disconnected_forced: return "disconnected_forced";
case sdv::ipc::EConnectState::terminating: return "terminating";
default: return "unknown";
}
}
@@ -86,11 +86,11 @@ CConnection::~CConnection()
#endif
// If still connected, disconnect.
if (m_eStatus == sdv::ipc::EConnectStatus::connected)
if (m_eConnectState == sdv::ipc::EConnectState::connected)
Disconnect();
// Set to terminating to allow the threads to shut down.
m_eStatus = sdv::ipc::EConnectStatus::terminating;
m_eConnectState = sdv::ipc::EConnectState::terminating;
// Stop the receive thread to prevent accepting any more messages from the server.
if (m_threadReceive.joinable())
@@ -146,19 +146,19 @@ uint32_t CConnection::Send(const void* pData, uint32_t uiDataLength)
#if ENABLE_REPORTING >= 2
switch (((SMsgHdr*)pData)->eType)
{
case EMsgType::sync_request: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND SYNC_REQUEST (", ConnectState(m_eStatus), ")"); break;
case EMsgType::sync_answer: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND SYNC_ANSWER (", ConnectState(m_eStatus), ")"); break;
case EMsgType::connect_request: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND CONNECT_REQUEST (", ConnectState(m_eStatus), ")"); break;
case EMsgType::connect_answer: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND CONNECT_ANSWER (", ConnectState(m_eStatus), ")"); break;
case EMsgType::connect_term: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND CONNECT_TERM (", ConnectState(m_eStatus), ")"); break;
case EMsgType::sync_request: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND SYNC_REQUEST (", ConnectState2String(m_eConnectState), ")"); break;
case EMsgType::sync_answer: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND SYNC_ANSWER (", ConnectState2String(m_eConnectState), ")"); break;
case EMsgType::connect_request: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND CONNECT_REQUEST (", ConnectState2String(m_eConnectState), ")"); break;
case EMsgType::connect_answer: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND CONNECT_ANSWER (", ConnectState2String(m_eConnectState), ")"); break;
case EMsgType::connect_term: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND CONNECT_TERM (", ConnectState2String(m_eConnectState), ")"); break;
#if ENABLE_REPORTING >= 3
case EMsgType::data: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND DATA ", uiDataLength - sizeof(SMsgHdr), " bytes (", ConnectState(m_eStatus), ")"); break;
case EMsgType::data_fragment: TRACE(m_bServer ? "SERVER" : "CLIENT", " RECEIVE DATA FRAGMENT ", uiDataLength - sizeof(SFragmentedMsgHdr), " bytes (", ConnectState(m_eStatus), ")"); break;
case EMsgType::data: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND DATA ", uiDataLength - sizeof(SMsgHdr), " bytes (", ConnectState2String(m_eConnectState), ")"); break;
case EMsgType::data_fragment: TRACE(m_bServer ? "SERVER" : "CLIENT", " RECEIVE DATA FRAGMENT ", uiDataLength - sizeof(SFragmentedMsgHdr), " bytes (", ConnectState2String(m_eConnectState), ")"); break;
#else
case EMsgType::data: break;
case EMsgType::data_fragment: break;
#endif
default: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND UNKNOWN (", ConnectState(m_eStatus), ")"); break;
default: TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND UNKNOWN (", ConnectState2String(m_eConnectState), ")"); break;
}
#endif
@@ -186,10 +186,10 @@ bool CConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqDa
}
TRACE("Send a sequence of data with ", seqData.size(), " pointers with the length {", sstreamReport.str(), "} bytes");
#endif
// Only allow sending messages when the status is connected
if (m_eStatus != sdv::ipc::EConnectStatus::connected)
// Only allow sending messages when the state is connected
if (m_eConnectState != sdv::ipc::EConnectState::connected)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return false;
}
@@ -260,7 +260,7 @@ bool CConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqDa
auto optPacket = m_sender.Reserve(uiAllocSize);
if (!optPacket)
{
if (m_eStatus == sdv::ipc::EConnectStatus::connected)
if (m_eConnectState == sdv::ipc::EConnectState::connected)
SDV_LOG_ERROR("Could not reserve a buffer to send a message of ", uiDataSize, " bytes.");
return false;
}
@@ -277,7 +277,7 @@ bool CConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqDa
uiMsgOffset = sizeof(SFragmentedMsgHdr);
#if ENABLE_REPORTING >= 3
TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND DATA FRAGMENT ", uiOffset, "-", uiOffset + uiDataSize - 1, " of ", uiRequiredSize, " bytes (", ConnectState(m_eStatus), ")");
TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND DATA FRAGMENT ", uiOffset, "-", uiOffset + uiDataSize - 1, " of ", uiRequiredSize, " bytes (", ConnectState2String(m_eConnectState), ")");
#endif
}
else
@@ -288,7 +288,7 @@ bool CConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqDa
uiMsgOffset = sizeof(SMsgHdr);
#if ENABLE_REPORTING >= 3
TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND DATA ", uiRequiredSize, " bytes (", ConnectState(m_eStatus), ")");
TRACE(m_bServer ? "SERVER" : "CLIENT", " SEND DATA ", uiRequiredSize, " bytes (", ConnectState2String(m_eConnectState), ")");
#endif
}
@@ -352,29 +352,29 @@ bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
std::unique_lock<std::mutex> lock(m_mtxConnect);
// Allowed to connect?
if (m_eStatus != sdv::ipc::EConnectStatus::uninitialized)
if (m_eConnectState != sdv::ipc::EConnectState::uninitialized)
{
for (auto& rprEventCallback : m_lstEventCallbacks)
if (rprEventCallback.pCallback && rprEventCallback.uiCookie)
rprEventCallback.pCallback->SetStatus(sdv::ipc::EConnectStatus::connection_error);
rprEventCallback.pCallback->SetConnectState(sdv::ipc::EConnectState::connection_error);
return false;
}
SetStatus(sdv::ipc::EConnectStatus::initializing);
SetConnectState(sdv::ipc::EConnectState::initializing);
// Initialized?
if (!m_sender.IsValid() && !m_receiver.IsValid())
{
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
SDV_LOG_ERROR("Could not establish connection: sender(", (m_sender.IsValid() ? "valid" : "invalid"), ") receiver(",
(m_receiver.IsValid() ? "valid" : "invalid"), ")");
SetStatus(sdv::ipc::EConnectStatus::uninitialized);
SetConnectState(sdv::ipc::EConnectState::uninitialized);
return false;
}
// Assign the receiver
m_pReceiver = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IDataReceiveCallback>();
SetStatus(sdv::ipc::EConnectStatus::initialized);
SetConnectState(sdv::ipc::EConnectState::initialized);
// Start the receiving thread (wait until started).
m_threadReceive = std::thread(&CConnection::ReceiveMessages, this);
@@ -397,13 +397,13 @@ bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
bool CConnection::WaitForConnection(/*in*/ uint32_t uiWaitMs)
{
#if ENABLE_REPORTING >= 1
if (m_eStatus == sdv::ipc::EConnectStatus::connected)
if (m_eConnectState == sdv::ipc::EConnectState::connected)
TRACE("Not waiting for a connection - already connected");
else
TRACE("Waiting for a connection of ", uiWaitMs, "ms");
#endif
if (m_eStatus == sdv::ipc::EConnectStatus::connected) return true;
if (m_eConnectState == sdv::ipc::EConnectState::connected) return true;
std::unique_lock<std::mutex> lock(m_mtxConnect);
@@ -413,13 +413,13 @@ bool CConnection::WaitForConnection(/*in*/ uint32_t uiWaitMs)
m_cvConnect.wait_for(lock, std::chrono::milliseconds(uiWaitMs));
#if ENABLE_REPORTING >= 1
if (m_eStatus == sdv::ipc::EConnectStatus::connected)
if (m_eConnectState == sdv::ipc::EConnectState::connected)
TRACE("Waiting finished - connection established");
else
TRACE("Waiting finished - timeout occurred");
#endif
return m_eStatus == sdv::ipc::EConnectStatus::connected;
return m_eConnectState == sdv::ipc::EConnectState::connected;
}
void CConnection::CancelWait()
@@ -440,19 +440,19 @@ void CConnection::Disconnect()
// Cancel any waits, just in case
CancelWait();
// Set the disconnect status
sdv::ipc::EConnectStatus eStatus = m_eStatus;
SetStatus(sdv::ipc::EConnectStatus::disconnected);
// Set the disconnect state
sdv::ipc::EConnectState eConnectState = m_eConnectState;
SetConnectState(sdv::ipc::EConnectState::disconnected);
// Release the interface
m_pReceiver = nullptr;
// If connected, send termination message.
switch (eStatus)
switch (eConnectState)
{
case sdv::ipc::EConnectStatus::connecting:
case sdv::ipc::EConnectStatus::negotiating:
case sdv::ipc::EConnectStatus::connected:
case sdv::ipc::EConnectState::connecting:
case sdv::ipc::EConnectState::negotiating:
case sdv::ipc::EConnectState::connected:
Send(SMsgHdr{ SDVFrameworkInterfaceVersion, EMsgType::connect_term });
break;
default:
@@ -464,7 +464,7 @@ void CConnection::Disconnect()
#endif
}
uint64_t CConnection::RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback)
uint64_t CConnection::RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback)
{
if (!pEventCallback) return 0;
sdv::ipc::IConnectEventCallback* pCallback = pEventCallback->GetInterface<sdv::ipc::IConnectEventCallback>();
@@ -477,7 +477,7 @@ uint64_t CConnection::RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess*
return uiCookie;
}
void CConnection::UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie)
void CConnection::UnregisterStateEventCallback(/*in*/ uint64_t uiCookie)
{
std::shared_lock<std::shared_mutex> lock(m_mtxEventCallbacks);
auto itEventCallback = std::find_if(m_lstEventCallbacks.begin(), m_lstEventCallbacks.end(),
@@ -486,9 +486,9 @@ void CConnection::UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie)
itEventCallback->pCallback = nullptr;
}
sdv::ipc::EConnectStatus CConnection::GetStatus() const
sdv::ipc::EConnectState CConnection::GetConnectState() const
{
return m_eStatus;
return m_eConnectState;
}
void CConnection::DestroyObject()
@@ -500,8 +500,8 @@ void CConnection::DestroyObject()
// Disconnect
Disconnect();
// Set termination status.
SetStatus(sdv::ipc::EConnectStatus::terminating);
// Set termination state.
SetConnectState(sdv::ipc::EConnectState::terminating);
// Clear all events callbacks (if not done so already)
std::shared_lock<std::shared_mutex> lock(m_mtxEventCallbacks);
@@ -523,32 +523,32 @@ void CConnection::DestroyObject()
#endif
}
void CConnection::SetStatus(sdv::ipc::EConnectStatus eStatus)
void CConnection::SetConnectState(sdv::ipc::EConnectState eConnectState)
{
#if ENABLE_REPORTING >= 1
TRACE(m_bServer ? "SERVER" : "CLIENT", " Changing connect status from '", ConnectState(m_eStatus), "' to '", ConnectState(eStatus), "'");
TRACE(m_bServer ? "SERVER" : "CLIENT", " Changing connect state from '", ConnectState2String(m_eConnectState), "' to '", ConnectState2String(eConnectState), "'");
#endif
// Do not change the status when terminated.
if (m_eStatus == sdv::ipc::EConnectStatus::terminating)
// Do not change the state when terminated.
if (m_eConnectState == sdv::ipc::EConnectState::terminating)
return;
// Only set the member variable if the status is not communication_error
if (eStatus != sdv::ipc::EConnectStatus::communication_error)
m_eStatus = eStatus;
// Only set the member variable if the state is not communication_error
if (eConnectState != sdv::ipc::EConnectState::communication_error)
m_eConnectState = eConnectState;
std::shared_lock<std::shared_mutex> lock(m_mtxEventCallbacks);
for (auto& rprEventCallback : m_lstEventCallbacks)
{
if (rprEventCallback.pCallback && rprEventCallback.uiCookie)
rprEventCallback.pCallback->SetStatus(eStatus);
rprEventCallback.pCallback->SetConnectState(eConnectState);
}
// If disconnected by force update the disconnect status.
if (m_eStatus == sdv::ipc::EConnectStatus::disconnected_forced)
m_eStatus = sdv::ipc::EConnectStatus::disconnected;
// If disconnected by force update the disconnect state.
if (m_eConnectState == sdv::ipc::EConnectState::disconnected_forced)
m_eConnectState = sdv::ipc::EConnectState::disconnected;
#if ENABLE_REPORTING >= 1
TRACE("Status updated...");
TRACE("State updated...");
#endif
}
@@ -574,7 +574,7 @@ void CConnection::ReceiveMessages()
if (!m_sender.IsValid() || !m_receiver.IsValid())
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_ERROR("No valid shared memory for receiving.");
lock.unlock();
m_cvStartConnect.notify_all();
@@ -590,7 +590,7 @@ void CConnection::ReceiveMessages()
std::chrono::high_resolution_clock::time_point tpLastReceiveLoop = std::chrono::high_resolution_clock::now();
#endif
SDataContext sDataCtxt;
while (m_eStatus != sdv::ipc::EConnectStatus::terminating)
while (m_eConnectState != sdv::ipc::EConnectState::terminating)
{
#ifdef TIME_TRACKING
std::chrono::high_resolution_clock::time_point tpTrackNow = std::chrono::high_resolution_clock::now();
@@ -604,7 +604,7 @@ void CConnection::ReceiveMessages()
{
// Start communication, but only if connection is client based. Server based should not start the communication. If
// there is no client, the server would otherwise fill its send-buffer. Repeat sending every 500ms.
if (!m_bServer && (/*m_eStatus == sdv::ipc::EConnectStatus::disconnected ||*/ m_eStatus == sdv::ipc::EConnectStatus::initialized))
if (!m_bServer && (/*m_eConnectState == sdv::ipc::EConnectState::disconnected ||*/ m_eConnectState == sdv::ipc::EConnectState::initialized))
{
// Send request
auto tpNow = std::chrono::high_resolution_clock::now();
@@ -629,7 +629,7 @@ void CConnection::ReceiveMessages()
if (!message.IsValid())
{
auto ssError = m_receiver.GetError();
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_ERROR("The message is invalid (invalid size or invalid type).");
continue;
}
@@ -638,7 +638,7 @@ void CConnection::ReceiveMessages()
message.PrintHeader(*this);
// Extra check to prevent race condition.
if (m_eStatus == sdv::ipc::EConnectStatus::terminating) break;
if (m_eConnectState == sdv::ipc::EConnectState::terminating) break;
#if ENABLE_REPORTING >= 1
switch (message.GetMsgHdr().eType)
@@ -846,7 +846,7 @@ bool CConnection::ReadDataChunk(CMessage& rMessage, uint32_t uiOffset, SDataCont
std::unique_lock<std::mutex> lockReceive(m_mtxReceive);
while (m_queueReceive.size() >= 16)
{
if (m_eStatus == sdv::ipc::EConnectStatus::terminating) return false;
if (m_eConnectState == sdv::ipc::EConnectState::terminating) return false;
m_cvReceiveProcessed.wait_for(lockReceive, std::chrono::milliseconds(100));
}
@@ -868,7 +868,7 @@ bool CConnection::ReadDataChunk(CMessage& rMessage, uint32_t uiOffset, SDataCont
#if ENABLE_DECOUPLING > 0
void CConnection::DecoupleReceive()
{
while (m_eStatus != sdv::ipc::EConnectStatus::terminating)
while (m_eConnectState != sdv::ipc::EConnectState::terminating)
{
// Wait for data
std::unique_lock<std::mutex> lock(m_mtxReceive);
@@ -887,7 +887,7 @@ void CConnection::DecoupleReceive()
size_t nSize = 0;
for (const sdv::pointer<uint8_t>& ptrData : seqData)
nSize += ptrData.size();
TRACE(m_bServer ? "SERVER" : "CLIENT", " DECOUPLED REVEICE DATA ", nSize, " bytes (", ConnectState(m_eStatus), ")");
TRACE(m_bServer ? "SERVER" : "CLIENT", " DECOUPLED REVEICE DATA ", nSize, " bytes (", ConnectState2String(m_eConnectState), ")");
#endif
// Process the data
@@ -903,28 +903,28 @@ void CConnection::ReceiveSyncRequest(const CMessage& rMessage)
{
if (rMessage.GetSize() != sizeof(SMsgHdr))
{
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
SDV_LOG_ERROR("Sync request received but with incorrect structure size ", rMessage.GetSize(), " in the request, but ",
sizeof(SMsgHdr), " needed!");
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
return;
}
// Check for compatibility
if (rMessage.GetMsgHdr().uiVersion != SDVFrameworkInterfaceVersion)
{
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
SDV_LOG_ERROR("Sync request received for an incompatible communication; interface version ", rMessage.GetMsgHdr().uiVersion,
" requested, but ", SDVFrameworkInterfaceVersion, " needed!");
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
return;
}
else
{
// Start connecting
if (m_eStatus == sdv::ipc::EConnectStatus::disconnected || m_eStatus == sdv::ipc::EConnectStatus::initialized)
if (m_eConnectState == sdv::ipc::EConnectState::disconnected || m_eConnectState == sdv::ipc::EConnectState::initialized)
{
SetStatus(sdv::ipc::EConnectStatus::connecting);
SetConnectState(sdv::ipc::EConnectState::connecting);
// Send an answer
Send(SMsgHdr{ SDVFrameworkInterfaceVersion, EMsgType::sync_answer });
@@ -935,18 +935,18 @@ void CConnection::ReceiveSyncRequest(const CMessage& rMessage)
void CConnection::ReceiveConnectRequest(const CMessage& rMessage)
{
// Start negotiating
if (m_eStatus == sdv::ipc::EConnectStatus::connecting)
if (m_eConnectState == sdv::ipc::EConnectState::connecting)
{
// The connect message contains the process ID to monitor.
m_rWatchDog.AddMonitor(rMessage.GetConnectHdr().tProcessID, this);
// Replay to the request
SetStatus(sdv::ipc::EConnectStatus::negotiating);
SetConnectState(sdv::ipc::EConnectState::negotiating);
Send(SConnectMsg{ {SDVFrameworkInterfaceVersion, EMsgType::connect_answer},
static_cast<sdv::process::TProcessID>(GetProcessID()) });
// Connected
SetStatus(sdv::ipc::EConnectStatus::connected);
SetConnectState(sdv::ipc::EConnectState::connected);
#if ENABLE_REPORTING >= 1
TRACE("Trigger connected");
#endif
@@ -956,34 +956,34 @@ void CConnection::ReceiveConnectRequest(const CMessage& rMessage)
void CConnection::ReceiveSyncAnswer(const CMessage& rMessage)
{
if (m_eStatus != sdv::ipc::EConnectStatus::disconnected && m_eStatus != sdv::ipc::EConnectStatus::initialized)
if (m_eConnectState != sdv::ipc::EConnectState::disconnected && m_eConnectState != sdv::ipc::EConnectState::initialized)
return;
// Check for compatibility
if (rMessage.GetMsgHdr().uiVersion != SDVFrameworkInterfaceVersion)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_ERROR("Sync answer received for an incompatible communication; interface version ",
rMessage.GetMsgHdr().uiVersion, " requested, but ", SDVFrameworkInterfaceVersion, " needed!");
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
return;
}
// Start negotiating
SetStatus(sdv::ipc::EConnectStatus::negotiating);
SetConnectState(sdv::ipc::EConnectState::negotiating);
Send(SConnectMsg{ {SDVFrameworkInterfaceVersion, EMsgType::connect_request}, GetProcessID() });
}
void CConnection::ReceiveConnectAnswer(const CMessage& rMessage)
{
// Connection established
if (m_eStatus == sdv::ipc::EConnectStatus::negotiating)
if (m_eConnectState == sdv::ipc::EConnectState::negotiating)
{
// The connect message contains the process ID to monitor.
m_rWatchDog.AddMonitor(rMessage.GetConnectHdr().tProcessID, this);
// Connected
SetStatus(sdv::ipc::EConnectStatus::connected);
SetConnectState(sdv::ipc::EConnectState::connected);
#if ENABLE_REPORTING >= 1
TRACE("Trigger connected...");
#endif
@@ -993,7 +993,7 @@ void CConnection::ReceiveConnectAnswer(const CMessage& rMessage)
void CConnection::ReceiveConnectTerm(CMessage& /*rMessage*/)
{
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
m_rWatchDog.RemoveMonitor(this);
// Cancel any outstanding write... and reset the read position of the sender (otherwise any outstanding data will have
@@ -1016,7 +1016,7 @@ void CConnection::ReceiveDataMessage(CMessage& rMessage, SDataContext& rsDataCtx
uint32_t uiOffset = ReadDataTable(rMessage, rsDataCtxt);
if (!uiOffset)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
@@ -1033,7 +1033,7 @@ void CConnection::ReceiveDataMessage(CMessage& rMessage, SDataContext& rsDataCtx
// Read data
if (!ReadDataChunk(rMessage, uiOffset, rsDataCtxt))
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
#if ENABLE_REPORTING >= 1
@@ -1057,7 +1057,7 @@ void CConnection::ReceiveDataFragementMessage(CMessage& rMessage, SDataContext&
uiOffset = ReadDataTable(rMessage, rsDataCtxt);
if (!uiOffset)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
@@ -1075,7 +1075,7 @@ void CConnection::ReceiveDataFragementMessage(CMessage& rMessage, SDataContext&
// Read data chunk
if (!ReadDataChunk(rMessage, uiOffset, rsDataCtxt))
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
}
@@ -1147,19 +1147,19 @@ void CConnection::CMessage::PrintHeader([[maybe_unused]] const CConnection& rCon
#if ENABLE_REPORTING >= 2
switch (GetMsgHdr().eType)
{
case EMsgType::sync_request: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE SYNC_REQUEST (", ConnectState(rConnection.GetStatus()), ")"); break;
case EMsgType::sync_answer: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE SYNC_ANSWER (", ConnectState(rConnection.GetStatus()), ")"); break;
case EMsgType::connect_request: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE CONNECT_REQUEST (", ConnectState(rConnection.GetStatus()), ")"); break;
case EMsgType::connect_answer: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE CONNECT_ANSWER (", ConnectState(rConnection.GetStatus()), ")"); break;
case EMsgType::connect_term: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE CONNECT_TERM (", ConnectState(rConnection.GetStatus()), ")"); break;
case EMsgType::sync_request: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE SYNC_REQUEST (", ConnectState2String(rConnection.GetConnectState()), ")"); break;
case EMsgType::sync_answer: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE SYNC_ANSWER (", ConnectState2String(rConnection.GetConnectState()), ")"); break;
case EMsgType::connect_request: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE CONNECT_REQUEST (", ConnectState2String(rConnection.GetConnectState()), ")"); break;
case EMsgType::connect_answer: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE CONNECT_ANSWER (", ConnectState2String(rConnection.GetConnectState()), ")"); break;
case EMsgType::connect_term: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE CONNECT_TERM (", ConnectState2String(rConnection.GetConnectState()), ")"); break;
#if ENABLE_REPORTING >= 3
case EMsgType::data: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE DATA ", GetSize() - sizeof(SMsgHdr), " bytes (", ConnectState(rConnection.GetStatus()), ")"); break;
case EMsgType::data_fragment: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE DATA FRAGMENT ", GetSize() - sizeof(SFragmentedMsgHdr), " bytes (", ConnectState(rConnection.GetStatus()), ")"); break;
case EMsgType::data: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE DATA ", GetSize() - sizeof(SMsgHdr), " bytes (", ConnectState2String(rConnection.GetConnectState()), ")"); break;
case EMsgType::data_fragment: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE DATA FRAGMENT ", GetSize() - sizeof(SFragmentedMsgHdr), " bytes (", ConnectState2String(rConnection.GetConnectState()), ")"); break;
#else
case EMsgType::data: break;
case EMsgType::data_fragment: break;
#endif
default: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE UNKNOWN version=", static_cast<uint32_t>(GetMsgHdr().uiVersion), " type=", static_cast<uint32_t>(GetMsgHdr().eType), "(", ConnectState(rConnection.GetStatus()), ")"); break;
default: rConnection.TRACE(rConnection.IsServer() ? "SERVER" : "CLIENT", " RECEIVE UNKNOWN version=", static_cast<uint32_t>(GetMsgHdr().uiVersion), " type=", static_cast<uint32_t>(GetMsgHdr().eType), "(", ConnectState2String(rConnection.GetConnectState()), ")"); break;
}
#endif
}

View File

@@ -106,7 +106,7 @@ public:
* @brief Establish a connection and start sending/receiving messages. Overload of
* sdv::ipc::IConnect::AsyncConnect.
* @param[in] pReceiver The message has to be forwarded.
* @return Returns 'true' when a connection could be established. Use IConnectStatus or IConnectEventCallback to check the
* @return Returns 'true' when a connection could be established. Use IConnect or IConnectEventCallback to check the
* connection state.
*/
virtual bool AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver) override;
@@ -127,33 +127,33 @@ public:
// Suppress cppcheck warning. The destructor calls Disconnect without dynamic binding. This is correct so.
// cppcheck-suppress virtualCallInConstructor
/**
* @brief Disconnect from a connection. This will set the connect status to disconnected. Overload of
* @brief Disconnect from a connection. This will set the connect state to disconnected. Overload of
* sdv::ipc::IConnect::Disconnect.
*/
virtual void Disconnect() override;
/**
* @brief Register event callback interface. Overload of sdv::ipc::IConnect::RegisterStatusEventCallback.
* @details Register a connection status event callback interface. The exposed interface must be of type
* @brief Register event callback interface. Overload of sdv::ipc::IConnect::RegisterStateEventCallback.
* @details Register a connection state event callback interface. The exposed interface must be of type
* IConnectEventCallback. The registration will exist until a call to the unregister function with the returned cookie
* or until the connection is terminated.
* @param[in] pEventCallback Pointer to the object exposing the IConnectEventCallback interface.
* @return The cookie assigned to the registration.
*/
virtual uint64_t RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
virtual uint64_t RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
/**
* @brief Unregister the status event callback with the returned cookie from the registration. Overload of
* sdv::ipc::IConnect::UnregisterStatusEventCallback.
* @brief Unregister the state event callback with the returned cookie from the registration. Overload of
* sdv::ipc::IConnect::UnregisterStateEventCallback.
* @param[in] uiCookie The cookie returned by a previous call to the registration function.
*/
virtual void UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie) override;
virtual void UnregisterStateEventCallback(/*in*/ uint64_t uiCookie) override;
/**
* @brief Get status of the connection. Overload of sdv::ipc::IConnect::GetStatus.
* @return Returns the ipc::EConnectStatus struct
* @brief Get the current state of the IPC conection. Overload of sdv::ipc::IConnect::GetConnectState.
* @return Returns connection state.
*/
virtual sdv::ipc::EConnectStatus GetStatus() const override;
virtual sdv::ipc::EConnectState GetConnectState() const override;
/**
* @brief Destroy the object. Overload of IObjectDestroy::DestroyObject.
@@ -162,10 +162,10 @@ public:
virtual void DestroyObject() override;
/**
* @brief Set the connection status and if needed call the event callback.
* @param[in] eStatus The new status.
* @brief Set the connection state and if needed call the event callback.
* @param[in] eConnectState The new state.
*/
void SetStatus(sdv::ipc::EConnectStatus eStatus);
void SetConnectState(sdv::ipc::EConnectState eConnectState);
/**
* @brief Returns whether this is a server connection or a client connection.
@@ -258,14 +258,14 @@ private:
CSharedMemBufferTx m_sender; ///< Shared buffer for sending.
CSharedMemBufferRx m_receiver; ///< Shared buffer for receiving.
std::thread m_threadReceive; ///< Thread which receives data from the socket.
std::atomic<sdv::ipc::EConnectStatus> m_eStatus = sdv::ipc::EConnectStatus::uninitialized; ///< the status of the connection
std::atomic<sdv::ipc::EConnectState> m_eConnectState = sdv::ipc::EConnectState::uninitialized; ///< the state of the connection
sdv::ipc::IDataReceiveCallback* m_pReceiver = nullptr; ///< Receiver to pass the messages to if available
std::shared_mutex m_mtxEventCallbacks; ///< Protect access to callback list. Only locking when
///< inserting.
std::list<SEventCallback> m_lstEventCallbacks; ///< List containing event callbacks. New callbacks will
///< be inserted in front (called first). Removed
///< callbacks are NULL; the entry stays to allow
///< removal during a SetStatus call.
///< removal during a SetConnectState call.
mutable std::mutex m_mtxSend; ///< Synchronize all packages to be sent.
std::mutex m_mtxConnect; ///< Connection mutex.
std::condition_variable m_cvConnect; ///< Connection variable for connecting.

View File

@@ -196,7 +196,7 @@ void CWatchDog::ProcessTerminated(/*in*/ sdv::process::TProcessID tProcessID, /*
// Inform the connection about the removed process.
for (auto& rptrConnection : vecDisconnectedConnections)
{
rptrConnection->SetStatus(sdv::ipc::EConnectStatus::disconnected_forced);
rptrConnection->SetConnectState(sdv::ipc::EConnectState::disconnected_forced);
#if ENABLE_REPORTING > 0
TRACE("Forced disconnection for PID#", tProcessID);

View File

@@ -13,16 +13,13 @@ if(UNIX)
project(uds_unix_sockets VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(uds_unix_sockets SHARED
"channel_mgnt.h"
"channel_mgnt.cpp"
"connection.h"
"connection.cpp"
add_library(uds_unix_sockets STATIC
channel_mgnt.cpp
connection.cpp
)
target_link_libraries(uds_unix_sockets rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
target_link_options(uds_unix_sockets PRIVATE)
target_include_directories(uds_unix_sockets PRIVATE ./include/)
set_target_properties(uds_unix_sockets PROPERTIES PREFIX "")
set_target_properties(uds_unix_sockets PROPERTIES SUFFIX ".sdv")

View File

@@ -105,7 +105,7 @@ CUnixSocketConnection::CUnixSocketConnection(int preconfiguredFd,
, m_UdsPath(udsPath)
, m_StopReceiveThread(false)
, m_StopConnectThread(false)
, m_eStatus(sdv::ipc::EConnectStatus::uninitialized)
, m_eConnectState(sdv::ipc::EConnectState::uninitialized)
, m_pReceiver(nullptr)
, m_pEvent(nullptr)
{
@@ -170,10 +170,10 @@ bool CUnixSocketConnection::SendData(sdv::sequence<sdv::pointer<uint8_t>>& seqDa
#endif
// Only send when connected and FD valid
if (m_eStatus != sdv::ipc::EConnectStatus::connected || m_Fd < 0)
if (m_eConnectState != sdv::ipc::EConnectState::connected || m_Fd < 0)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SDV_LOG_WARNING("[UDS][TX] Send requested while not connected or FD invalid (status=", static_cast<int>(m_eStatus.load()), ")");
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][TX] Send requested while not connected or FD invalid (state=", static_cast<int>(m_eConnectState.load()), ")");
return false;
}
@@ -328,7 +328,7 @@ bool CUnixSocketConnection::SendData(sdv::sequence<sdv::pointer<uint8_t>>& seqDa
return true;
}
uint64_t CUnixSocketConnection::RegisterStatusEventCallback(sdv::IInterfaceAccess* pEventCallback)
uint64_t CUnixSocketConnection::RegisterStateEventCallback(sdv::IInterfaceAccess* pEventCallback)
{
if (!pEventCallback) return 0;
@@ -351,7 +351,7 @@ uint64_t CUnixSocketConnection::RegisterStatusEventCallback(sdv::IInterfaceAcces
return cookie;
}
void CUnixSocketConnection::UnregisterStatusEventCallback(uint64_t uiCookie)
void CUnixSocketConnection::UnregisterStateEventCallback(uint64_t uiCookie)
{
if (!uiCookie) return;
@@ -386,7 +386,7 @@ bool CUnixSocketConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
std::lock_guard<std::mutex> lk(m_StateMtx);
m_pReceiver = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IDataReceiveCallback>();
m_pEvent = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IConnectEventCallback>();
m_eStatus = sdv::ipc::EConnectStatus::initializing;
m_eConnectState = sdv::ipc::EConnectState::initializing;
// Reset stop flags
m_StopReceiveThread.store(false);
@@ -450,22 +450,22 @@ int CUnixSocketConnection::AcceptConnection() // deprecated
bool CUnixSocketConnection::WaitForConnection(uint32_t uiWaitMs)
{
if (m_eStatus.load(std::memory_order_acquire) == sdv::ipc::EConnectStatus::connected) return true;
if (m_eConnectState.load(std::memory_order_acquire) == sdv::ipc::EConnectState::connected) return true;
std::unique_lock<std::mutex> lk(m_MtxConnect);
if (uiWaitMs == 0xFFFFFFFFu)
{
m_CvConnect.wait(lk, [this]{
return m_eStatus.load(std::memory_order_acquire) == sdv::ipc::EConnectStatus::connected;
return m_eConnectState.load(std::memory_order_acquire) == sdv::ipc::EConnectState::connected;
});
return true;
}
if (uiWaitMs == 0u)
return (m_eStatus.load(std::memory_order_acquire) == sdv::ipc::EConnectStatus::connected);
return (m_eConnectState.load(std::memory_order_acquire) == sdv::ipc::EConnectState::connected);
return m_CvConnect.wait_for(lk, std::chrono::milliseconds(uiWaitMs),
[this]{ return m_eStatus.load(std::memory_order_acquire) == sdv::ipc::EConnectStatus::connected; });
[this]{ return m_eConnectState.load(std::memory_order_acquire) == sdv::ipc::EConnectState::connected; });
}
void CUnixSocketConnection::CancelWait()
@@ -476,33 +476,33 @@ void CUnixSocketConnection::CancelWait()
void CUnixSocketConnection::Disconnect()
{
StopThreadsAndCloseSockets(/*unlinkPath*/ false);
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
}
sdv::ipc::EConnectStatus CUnixSocketConnection::GetStatus() const
sdv::ipc::EConnectState CUnixSocketConnection::GetConnectState() const
{
return m_eStatus;
return m_eConnectState;
}
void CUnixSocketConnection::DestroyObject()
{
m_StopReceiveThread.store(true);
m_eStatus = sdv::ipc::EConnectStatus::disconnected;
m_eConnectState = sdv::ipc::EConnectState::disconnected;
}
void CUnixSocketConnection::SetStatus(sdv::ipc::EConnectStatus eStatus)
void CUnixSocketConnection::SetConnectState(sdv::ipc::EConnectState eConnectState)
{
// Update internal state atomically and wake up waiters.
{
std::lock_guard<std::mutex> lk(m_MtxConnect);
m_eStatus.store(eStatus, std::memory_order_release);
m_eConnectState.store(eConnectState, std::memory_order_release);
}
m_CvConnect.notify_all();
// Notify the legacy single-listener (kept for backward compatibility).
try
{
m_pEvent->SetStatus(eStatus);
m_pEvent->SetConnectState(eConnectState);
}
catch (...) { /* swallow: user callback must not crash transport */ }
@@ -515,7 +515,7 @@ void CUnixSocketConnection::SetStatus(sdv::ipc::EConnectStatus eStatus)
if (!entry.pCallback) { needCompact = true; continue; }
try
{
entry.pCallback->SetStatus(eStatus);
entry.pCallback->SetConnectState(eConnectState);
}
catch (...) { /* swallow per-listener */ }
}
@@ -588,7 +588,7 @@ void CUnixSocketConnection::ConnectWorker()
if (!EnsureDir(dir))
{
SDV_LOG_ERROR("[UDS][Server] ensure_dir('", dir, "') failed: ", std::strerror(errno));
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
@@ -596,7 +596,7 @@ void CUnixSocketConnection::ConnectWorker()
if (m_ListenFd < 0)
{
SDV_LOG_ERROR("[UDS][Server] socket() failed: ", std::strerror(errno));
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
@@ -608,7 +608,7 @@ void CUnixSocketConnection::ConnectWorker()
{
SDV_LOG_ERROR("[UDS][Server] bind('", m_UdsPath, "') failed: ", std::strerror(errno));
::close(m_ListenFd); m_ListenFd = -1;
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
@@ -618,7 +618,7 @@ void CUnixSocketConnection::ConnectWorker()
{
SDV_LOG_ERROR("[UDS][Server] listen() failed: ", std::strerror(errno));
::close(m_ListenFd); m_ListenFd = -1;
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
@@ -670,12 +670,12 @@ void CUnixSocketConnection::ConnectWorker()
}
if (clientFd < 0)
{
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
m_Fd = clientFd;
SetStatus(sdv::ipc::EConnectStatus::connected);
SetConnectState(sdv::ipc::EConnectState::connected);
StartReceiveThread_Unsafe();
return;
}
@@ -686,7 +686,7 @@ void CUnixSocketConnection::ConnectWorker()
if (m_Fd < 0)
{
SDV_LOG_ERROR("[UDS][Client] socket() failed: ", std::strerror(errno));
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
@@ -702,7 +702,7 @@ void CUnixSocketConnection::ConnectWorker()
{
if (::connect(m_Fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == 0)
{
SetStatus(sdv::ipc::EConnectStatus::connected);
SetConnectState(sdv::ipc::EConnectState::connected);
#if ENABLE_REPORTING >= 1
TRACE("[UDS][Client] Connected");
#endif
@@ -719,19 +719,19 @@ void CUnixSocketConnection::ConnectWorker()
}
::close(m_Fd); m_Fd = -1;
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
}
catch (const std::exception& ex)
{
SDV_LOG_ERROR("[UDS][ConnectWorker] exception: ", ex.what());
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
}
catch (...)
{
SDV_LOG_ERROR("[UDS][ConnectWorker] unknown exception");
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
}
}
@@ -741,7 +741,7 @@ void CUnixSocketConnection::ReceiveSyncAnswer(const CMessage& message)
const auto hdr = message.GetMsgHdr();
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][RX] sync_answer with invalid version");
return;
}
@@ -772,13 +772,13 @@ void CUnixSocketConnection::ReceiveMessages()
while (!m_StopReceiveThread.load())
{
if (m_eStatus == sdv::ipc::EConnectStatus::terminating) break;
if (m_eConnectState == sdv::ipc::EConnectState::terminating) break;
// Snapshot FD
const int fd = m_Fd;
if (fd < 0)
{
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
SDV_LOG_WARNING("[UDS][RX] FD invalidated -> disconnected");
break;
}
@@ -789,7 +789,7 @@ void CUnixSocketConnection::ReceiveMessages()
if (pr == 0)
{
if (!m_AcceptConnectionRequired && (m_eStatus == sdv::ipc::EConnectStatus::initialized))
if (!m_AcceptConnectionRequired && (m_eConnectState == sdv::ipc::EConnectState::initialized))
{
auto now = std::chrono::high_resolution_clock::now();
if (std::chrono::duration<double>(now - tpStart).count() > 0.5)
@@ -805,7 +805,7 @@ void CUnixSocketConnection::ReceiveMessages()
if (pr < 0 || (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0)
{
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
SDV_LOG_WARNING("[UDS][RX] poll() hangup/error -> disconnected");
break;
}
@@ -815,7 +815,7 @@ void CUnixSocketConnection::ReceiveMessages()
uint32_t packetSize = 0;
if (!ReadTransportHeader(packetSize))
{
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
SDV_LOG_WARNING("[UDS][RX] Invalid/missing transport header -> disconnected");
break;
}
@@ -824,7 +824,7 @@ void CUnixSocketConnection::ReceiveMessages()
std::vector<uint8_t> payload(packetSize);
if (!ReadNumberOfBytes(reinterpret_cast<char*>(payload.data()), packetSize))
{
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
SDV_LOG_WARNING("[UDS][RX] Incomplete payload read -> disconnected");
break;
}
@@ -832,12 +832,12 @@ void CUnixSocketConnection::ReceiveMessages()
CMessage msg(std::move(payload));
if (!msg.IsValid())
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][RX] Invalid SDV message (envelope)");
continue;
}
if (m_eStatus == sdv::ipc::EConnectStatus::terminating) break;
if (m_eConnectState == sdv::ipc::EConnectState::terminating) break;
#if ENABLE_REPORTING >= 1
switch (msg.GetMsgHdr().eType)
@@ -874,12 +874,12 @@ void CUnixSocketConnection::ReceiveMessages()
catch (const std::exception& ex)
{
SDV_LOG_ERROR("[UDS][RX] exception: ", ex.what());
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
}
catch (...)
{
SDV_LOG_ERROR("[UDS][RX] unknown exception");
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
}
}
@@ -888,7 +888,7 @@ void CUnixSocketConnection::ReceiveSyncRequest(const CMessage& message)
const auto hdr = message.GetMsgHdr();
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][RX] sync_request with invalid version");
return;
}
@@ -907,7 +907,7 @@ void CUnixSocketConnection::ReceiveConnectRequest(const CMessage& message)
const auto hdr = message.GetConnectHdr();
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][RX] connect_request with invalid version");
return;
}
@@ -927,19 +927,19 @@ void CUnixSocketConnection::ReceiveConnectAnswer(const CMessage& message)
const auto hdr = message.GetConnectHdr();
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][RX] connect_answer with invalid version");
return;
}
// Fully established
SetStatus(sdv::ipc::EConnectStatus::connected);
SetConnectState(sdv::ipc::EConnectState::connected);
}
void CUnixSocketConnection::ReceiveConnectTerm(const CMessage& /*message*/)
{
// Peer requested termination
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
m_StopReceiveThread.store(true);
}
@@ -999,7 +999,7 @@ void CUnixSocketConnection::ReceiveDataMessage(const CMessage& rMessage, SDataCo
uint32_t uiOffset = ReadDataTable(rMessage, rsDataCtxt);
if (!uiOffset)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][RX] Invalid data table");
return;
}
@@ -1012,7 +1012,7 @@ void CUnixSocketConnection::ReceiveDataMessage(const CMessage& rMessage, SDataCo
if (!ReadDataChunk(rMessage, uiOffset, rsDataCtxt))
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][RX] Failed to read data chunk");
return;
}
@@ -1034,7 +1034,7 @@ void CUnixSocketConnection::ReceiveDataFragmentMessage(const CMessage& rMessage,
uiOffset = ReadDataTable(rMessage, rsDataCtxt);
if (!uiOffset)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][RX] Invalid fragmented data table");
return;
}
@@ -1048,7 +1048,7 @@ void CUnixSocketConnection::ReceiveDataFragmentMessage(const CMessage& rMessage,
if (!ReadDataChunk(rMessage, uiOffset, rsDataCtxt))
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][RX] Failed to read fragmented chunk");
return;
}

View File

@@ -82,24 +82,24 @@ public:
/** @brief Optionally cancel WaitForConnection (no-op in current implementation). */
void CancelWait() override;
/** @brief Disconnect and teardown threads/FDs; sets status to 'disconnected'. */
/** @brief Disconnect and teardown threads/FDs; sets state to 'disconnected'. */
void Disconnect() override;
// ---------- IConnect: event callbacks ----------
/** @brief Register a status event callback (no-op storage in UDS). */
uint64_t RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
/** @brief Register a state event callback (no-op storage in UDS). */
uint64_t RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
/** @brief Unregister a previously registered callback (no-op storage in UDS). */
void UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie) override;
void UnregisterStateEventCallback(/*in*/ uint64_t uiCookie) override;
/** @brief Get current connection status. */
sdv::ipc::EConnectStatus GetStatus() const override;
/** @brief Get current connection state. */
sdv::ipc::EConnectState GetConnectState() const override;
/** @brief Destroy object (IObjectDestroy). */
void DestroyObject() override;
/** @brief Set status and notify listeners (callback-safe). */
void SetStatus(sdv::ipc::EConnectStatus eStatus);
/** @brief Set state and notify listeners (callback-safe). */
void SetConnectState(sdv::ipc::EConnectState eConnectState);
/** @brief @return true if this side is server (needs accept()), false otherwise. */
bool IsServer() const;
@@ -297,7 +297,7 @@ public:
* @brief Handle an incoming connect_term message.
*
* Indicates that the peer requests immediate termination of the connection.
* Sets status to disconnected and stops the RX loop.
* Sets state to disconnected and stops the RX loop.
*
* @param message SDV envelope containing the connect_term header.
*/
@@ -352,7 +352,7 @@ public:
// ---------- Internal threading ----------
/** @brief Connect worker (server accept loop or client connect retry). */
void ConnectWorker();
/** @brief Start RX thread (precondition: status=connected, FD valid). */
/** @brief Start RX thread (precondition: state=connected, FD valid). */
void StartReceiveThread_Unsafe();
/**
* @brief Stop workers and close sockets, then optionally unlink path.
@@ -373,9 +373,9 @@ private:
std::thread m_ReceiveThread;
std::thread m_ConnectThread;
//Status & synchronization
//State & synchronization
std::condition_variable m_StateCv;
std::atomic<sdv::ipc::EConnectStatus> m_eStatus { sdv::ipc::EConnectStatus::uninitialized };
std::atomic<sdv::ipc::EConnectState> m_eConnectState { sdv::ipc::EConnectState::uninitialized };
sdv::ipc::IDataReceiveCallback* m_pReceiver { nullptr };
sdv::ipc::IConnectEventCallback* m_pEvent { nullptr };
std::mutex m_MtxConnect;

View File

@@ -0,0 +1,33 @@
#*******************************************************************************
# 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(UNIX)
# Define project
project(uds_unix_tunnel VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(uds_unix_tunnel STATIC
channel_mgnt.cpp
connection.cpp
)
target_link_libraries(uds_unix_tunnel rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
target_include_directories(uds_unix_tunnel PRIVATE ./include/)
set_target_properties(uds_unix_tunnel PROPERTIES PREFIX "")
set_target_properties(uds_unix_tunnel PROPERTIES SUFFIX ".sdv")
# Build dependencies
add_dependencies(uds_unix_tunnel CompileCoreIDL)
# Appending the service in the service list
set(SDV_Service_List ${SDV_Service_List} uds_unix_tunnel PARENT_SCOPE)
endif()

View File

@@ -0,0 +1,178 @@
#if defined(__unix__)
#include "channel_mgnt.h"
#include "connection.h" // CUnixTunnelConnection
#include "../sdv_services/uds_unix_sockets/connection.h" // CUnixSocketConnection
#include <support/toml.h>
#include <sstream>
#include <map>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
namespace
{
/**
* @brief Parses a semicolon-separated list of key=value pairs into a map.
*
* Example input: "proto=tunnel;role=server;path=/tmp/tunnel.sock;"
* Example output: { {"proto","tunnel"}, {"role","server"}, {"path","/tmp/tunnel.sock"} }
*
* @param s The input string to parse.
* @return Map of key-value pairs.
*/
static std::map<std::string, std::string> ParseKV(const std::string& s)
{
std::map<std::string, std::string> kv;
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, ';'))
{
auto pos = item.find('=');
if (pos != std::string::npos)
kv[item.substr(0, pos)] = item.substr(pos + 1);
}
return kv;
}
/**
* @brief Clamps an AF_UNIX pathname to the maximum allowed size for sockaddr_un::sun_path.
*
* @param p The path to clamp.
* @return The clamped path string.
*/
static std::string ClampSunPath(const std::string& p)
{
constexpr size_t MaxLen = sizeof(sockaddr_un::sun_path);
return (p.size() < MaxLen) ? p : p.substr(0, MaxLen - 1);
}
} // anonymous namespace
std::string CUnixTunnelChannelMgnt::MakeUserRuntimeDir()
{
std::ostringstream oss;
oss << "/run/user/" << ::getuid();
struct stat st{};
if (::stat(oss.str().c_str(), &st) == 0)
{
std::string path = oss.str() + "/sdv";
::mkdir(path.c_str(), 0770);
return path;
}
::mkdir("/tmp/sdv", 0770);
return "/tmp/sdv";
}
bool CUnixTunnelChannelMgnt::OnInitialize()
{
return true;
}
void CUnixTunnelChannelMgnt::OnShutdown()
{
// Actual cleanup is handled by destructors of CUnixTunnelConnection
// and CUnixSocketConnection (shared_ptr).
}
sdv::ipc::SChannelEndpoint CUnixTunnelChannelMgnt::CreateEndpoint(
const sdv::u8string& ssChannelConfig)
{
sdv::ipc::SChannelEndpoint endpoint{};
const std::string baseDir = MakeUserRuntimeDir();
std::string name = "TUNNEL_" + std::to_string(::getpid());
std::string path = baseDir + "/" + name + ".sock";
// Parse optional TOML config for custom name/path
if (!ssChannelConfig.empty())
{
sdv::toml::CTOMLParser cfg(ssChannelConfig.c_str());
auto nameNode = cfg.GetDirect("IpcChannel.Name");
if (nameNode.GetType() == sdv::toml::ENodeType::node_string)
name = static_cast<std::string>(nameNode.GetValue());
auto pathNode = cfg.GetDirect("IpcChannel.Path");
if (pathNode.GetType() == sdv::toml::ENodeType::node_string)
path = static_cast<std::string>(pathNode.GetValue());
else
path = baseDir + "/" + name + ".sock";
}
path = ClampSunPath(path);
// Create underlying UDS server transport
auto udsServer = std::make_shared<CUnixSocketConnection>(
-1,
/*acceptConnectionRequired*/ true,
path);
// Create tunnel wrapper on top of UDS
auto tunnelServer = std::make_shared<CUnixTunnelConnection>(
udsServer,
/*channelId*/ 0u);
m_ServerTunnels.push_back(tunnelServer);
endpoint.pConnection = static_cast<sdv::IInterfaceAccess*>(tunnelServer.get());
endpoint.ssConnectString = "proto=tunnel;role=server;path=" + path + ";";
return endpoint;
}
sdv::IInterfaceAccess* CUnixTunnelChannelMgnt::Access(
const sdv::u8string& ssConnectString)
{
const auto kv = ParseKV(static_cast<std::string>(ssConnectString));
// Only handle proto=tunnel
if (!kv.count("proto") || kv.at("proto") != "tunnel")
{
return nullptr;
}
const bool isServer =
(kv.count("role") && kv.at("role") == "server");
const std::string path =
kv.count("path")
? kv.at("path")
: (MakeUserRuntimeDir() + "/TUNNEL_auto.sock");
if (isServer)
{
// For simplicity, create a new server tunnel instance for each Access().
// The SDV framework is expected to call Access(serverCS) only once in normal cases.
auto udsServer = std::make_shared<CUnixSocketConnection>(
-1,
/*acceptConnectionRequired*/ true,
path);
auto tunnelServer = std::make_shared<CUnixTunnelConnection>(
udsServer,
/*channelId*/ 0u);
m_ServerTunnels.push_back(tunnelServer);
return static_cast<sdv::IInterfaceAccess*>(tunnelServer.get());
}
// Client: allocate raw pointer (expected to be managed by SDV framework via IObjectDestroy)
auto udsClient = std::make_shared<CUnixSocketConnection>(
-1,
/*acceptConnectionRequired*/ false,
path);
auto* tunnelClient =
new CUnixTunnelConnection(udsClient, /*channelId*/ 0u);
return static_cast<sdv::IInterfaceAccess*>(tunnelClient);
}
#endif // defined(__unix__)

View File

@@ -0,0 +1,140 @@
/********************************************************************************
* 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__)
#ifndef UNIX_TUNNEL_CHANNEL_MGNT_H
#define UNIX_TUNNEL_CHANNEL_MGNT_H
#include <support/component_impl.h>
#include <interfaces/ipc.h>
#include "../sdv_services/uds_unix_sockets/channel_mgnt.h" // existing UDS transport
class CUnixTunnelConnection;
/**
* @brief Initialize WinSock on Windows (idempotent).
*
* This helper ensures WSAStartup() is called only once in the process.
* On non-Windows platforms, this is a no-op and always returns success.
*
* @return 0 on success, otherwise a WinSock error code (Windows only).
*/
inline int StartUpWinSock()
{
#ifdef _WIN32
static bool isInitialized = false;
if (isInitialized)
{
return 0;
}
WSADATA wsaData {};
const int error = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (error != 0)
{
SDV_LOG_ERROR("WSAStartup failed with error: ", std::to_string(error));
}
else
{
SDV_LOG_INFO("WSAStartup initialized");
isInitialized = true;
}
return error;
#else
// Non-Windows: nothing to do. Return success for symmetry
return 0;
#endif
}
/**
* @class CUnixTunnelChannelMgnt
* @brief IPC channel management class for Unix Domain Socket tunnel communication.
*
* This manager exposes the "tunnel" IPC type, similar to UDS, with channel multiplexing planned.
* It provides creation and access to tunnel endpoints, manages server-side tunnel lifetimes,
* and integrates with the SDV object/component framework.
*/
class CUnixTunnelChannelMgnt :
public sdv::CSdvObject,
public sdv::ipc::ICreateEndpoint,
public sdv::ipc::IChannelAccess
{
public:
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::ipc::IChannelAccess)
SDV_INTERFACE_ENTRY(sdv::ipc::ICreateEndpoint)
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("UnixTunnelChannelControl")
DECLARE_OBJECT_CLASS_ALIAS("TunnelChannelControl")
DECLARE_DEFAULT_OBJECT_NAME("TunnelChannelControl")
DECLARE_OBJECT_SINGLETON()
/**
* @brief Destructor for CUnixTunnelChannelMgnt.
*/
virtual ~CUnixTunnelChannelMgnt() = default;
/**
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
* @return Returns 'true' when the initialization was successful, 'false' when not.
*/
virtual bool OnInitialize() override;
/**
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
*/
virtual void OnShutdown() override;
/**
* @brief Creates a tunnel endpoint (server side) and returns endpoint info.
*
* Optionally uses a TOML config string for custom endpoint parameters.
*
* @param ssChannelConfig Optional config string (TOML).
* @return The channel endpoint structure.
*/
sdv::ipc::SChannelEndpoint CreateEndpoint(const sdv::u8string& ssChannelConfig) override;
/**
* @brief Creates or accesses a connection object from the channel connect string.
*
* Parses the connect string and returns a pointer to the appropriate connection access interface.
*
* @param ssConnectString The channel connect string.
* @return Pointer to connection access interface.
*/
sdv::IInterfaceAccess* Access(const sdv::u8string& ssConnectString) override;
private:
/**
* @brief Helper: chooses runtime dir (/run/user/<uid>/sdv) or fallback (/tmp/sdv).
*
* Used for determining the directory path for runtime sockets.
*
* @return Directory path for runtime sockets.
*/
static std::string MakeUserRuntimeDir();
/**
* @brief Keeps server-side tunnel connections alive for the lifetime of the manager.
*
* This ensures that server tunnel objects are not destroyed while the manager is active.
*/
std::vector<std::shared_ptr<CUnixTunnelConnection>> m_ServerTunnels;
};
DEFINE_SDV_OBJECT(CUnixTunnelChannelMgnt)
#endif // UNIX_TUNNEL_CHANNEL_MGNT_H
#endif // defined(__unix__)

View File

@@ -0,0 +1,230 @@
/********************************************************************************
* 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 "connection.h"
/**
* @brief Constructs a tunnel connection wrapper on top of an existing UDS transport.
* @param transport Shared pointer to the underlying UDS transport.
* @param channelId Logical channel ID for this tunnel instance.
*/
CUnixTunnelConnection::CUnixTunnelConnection(
std::shared_ptr<CUnixSocketConnection> transport,
uint16_t channelId)
: m_Transport(std::move(transport))
, m_ChannelId(channelId)
{
// No additional initialization required; acts as a thin wrapper.
}
/**
* @brief Prepends a tunnel header and forwards the data to the underlying transport.
* @param seqData Sequence of message buffers to send (may be modified).
* @return true if data was sent successfully, false otherwise.
*/
bool CUnixTunnelConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
{
if (!m_Transport)
{
return false;
}
// Build tunnel header buffer
sdv::pointer<uint8_t> hdrBuf;
hdrBuf.resize(sizeof(STunnelHeader));
STunnelHeader hdr{};
hdr.uiChannelId = m_ChannelId; // Logical channel for this connection
hdr.uiFlags = 0; // Reserved for future use
// Copy header structure into the first buffer (little-endian host layout)
std::memcpy(hdrBuf.get(), &hdr, sizeof(STunnelHeader));
// Compose new sequence: [header] + original payload chunks
sdv::sequence<sdv::pointer<uint8_t>> seqWithHdr;
seqWithHdr.push_back(hdrBuf);
for (auto& chunk : seqData)
{
seqWithHdr.push_back(chunk);
}
return m_Transport->SendData(seqWithHdr);
}
/**
* @brief Starts asynchronous connect and registers upper-layer callbacks.
* @param pReceiver Pointer to callback interface for data and state notifications.
* @return true if connect started, false otherwise.
*/
bool CUnixTunnelConnection::AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver)
{
if (!m_Transport)
{
return false;
}
// Store upper-layer callbacks (safe for null)
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
sdv::TInterfaceAccessPtr acc(pReceiver);
m_pUpperReceiver = acc.GetInterface<sdv::ipc::IDataReceiveCallback>();
m_pUpperEvent = acc.GetInterface<sdv::ipc::IConnectEventCallback>();
}
// Register this tunnel as the data/event receiver in the UDS transport.
// Do NOT pass pReceiver to UDS, only to our upper fields!
return m_Transport->AsyncConnect(this);
}
bool CUnixTunnelConnection::WaitForConnection(/*in*/ uint32_t uiWaitMs)
{
if (!m_Transport)
{
return false;
}
return m_Transport->WaitForConnection(uiWaitMs);
}
void CUnixTunnelConnection::CancelWait()
{
if (!m_Transport)
{
return;
}
m_Transport->CancelWait();
}
void CUnixTunnelConnection::Disconnect()
{
if (!m_Transport)
{
return;
}
m_Transport->Disconnect();
// Clear upper-layer callbacks (thread-safe)
std::lock_guard<std::mutex> lock(m_CallbackMtx);
m_pUpperReceiver = nullptr;
m_pUpperEvent = nullptr;
}
uint64_t CUnixTunnelConnection::RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback)
{
if (!m_Transport)
{
return 0ULL;
}
// Directly forward to the underlying transport. This allows external
// components to receive connect-state changes without the tunnel
// having to implement IConnectEventCallback itself
return m_Transport->RegisterStateEventCallback(pEventCallback);
}
void CUnixTunnelConnection::UnregisterStateEventCallback(/*in*/ uint64_t uiCookie)
{
if (!m_Transport || uiCookie == 0ULL)
{
return;
}
m_Transport->UnregisterStateEventCallback(uiCookie);
}
sdv::ipc::EConnectState CUnixTunnelConnection::GetConnectState() const
{
if (!m_Transport)
{
// Reasonable default if transport is missing
return sdv::ipc::EConnectState::uninitialized;
}
return m_Transport->GetConnectState();
}
void CUnixTunnelConnection::DestroyObject()
{
// Disconnect underlying transport and clear callbacks.
Disconnect();
std::lock_guard<std::mutex> lock(m_CallbackMtx);
m_Transport.reset();
}
void CUnixTunnelConnection::ReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
{
#ifdef DEBUG_TUNNEL_RECEIVE
// Optional debug: count every call and print buffer size.
static std::atomic<uint64_t> s_counter{0};
auto id = ++s_counter;
std::cerr << "[Tunnel] ReceiveData call #" << id
<< ", seqData.size=" << seqData.size() << std::endl;
#endif
if (seqData.empty())
{
return;
}
// Extract and validate tunnel header, then remove it
const auto& hdrChunk = seqData[0];
if (hdrChunk.size() < sizeof(STunnelHeader))
{
return;
}
STunnelHeader hdr{};
std::memcpy(&hdr, hdrChunk.get(), sizeof(STunnelHeader));
seqData.erase(seqData.begin()); // remove header chunk
// Forward rest of data to upper-layer receiver (set by AsyncConnect)
sdv::ipc::IDataReceiveCallback* upper = nullptr;
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
upper = m_pUpperReceiver;
}
if (upper)
{
upper->ReceiveData(seqData);
}
}
void CUnixTunnelConnection::SetChannelId(uint16_t channelId)
{
m_ChannelId = channelId;
}
void CUnixTunnelConnection::SetConnectState(sdv::ipc::EConnectState state)
{
sdv::ipc::IConnectEventCallback* upper = nullptr;
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
upper = m_pUpperEvent;
}
if (upper)
{
try
{
upper->SetConnectState(state);
}
catch (...)
{
// Never let user callback crash the transport.
}
}
}
#endif // defined(__unix__)

View File

@@ -0,0 +1,191 @@
/********************************************************************************
* 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__)
#ifndef UNIX_SOCKET_TUNNEL_CONNECTION_H
#define UNIX_SOCKET_TUNNEL_CONNECTION_H
#include <interfaces/ipc.h>
#include <support/component_impl.h>
#include <support/interface_ptr.h>
#include <atomic>
#include <cstdint>
#include <mutex>
#include <memory>
#include <thread>
#include <vector>
#include <unordered_map>
#include "../sdv_services/uds_unix_sockets/connection.h" // existing UDS transport
/**
* @class CUnixTunnelConnection
* @brief Logical tunnel connection on top of a shared Unix Domain Socket (UDS) transport.
*
* This class wraps an existing CUnixSocketConnection (physical tunnel port) and provides
* optional tunneling capabilities such as a prepended tunnel header (channelId, flags).
* It is designed to enable demultiplexing of incoming payloads per logical channel in future versions.
*
* Currently, this acts as a simple pass-through wrapper. Tunnel header parsing and multi-channel
* routing are planned as future improvements (see TODOs in implementation).
*/
class CUnixTunnelConnection :
public sdv::IInterfaceAccess,
public sdv::IObjectDestroy,
public sdv::ipc::IDataSend,
public sdv::ipc::IConnect,
public sdv::ipc::IDataReceiveCallback,
public sdv::ipc::IConnectEventCallback
{
public:
/**
* @struct STunnelHeader
* @brief Small header prepended to each tunneled SDV message.
*
* Used for logical channel identification and control flags. This enables future support for
* multiplexing and advanced features (QoS, direction, etc.).
*/
struct STunnelHeader
{
uint16_t uiChannelId; ///< Logical channel ID (e.g., IPC_x / REMOTE_IPC_x)
uint16_t uiFlags; ///< Reserved for future use (QoS, direction, etc.)
};
/**
* @brief Constructs a tunnel connection wrapper.
* @param transport Shared pointer to the underlying UDS transport (physical tunnel port).
* @param channelId Default logical channel ID for this object view (may be ignored if full demux is implemented later).
*/
explicit CUnixTunnelConnection(
std::shared_ptr<CUnixSocketConnection> transport,
uint16_t channelId);
/**
* @brief Destructor. Cleans up resources if needed.
*/
virtual ~CUnixTunnelConnection() = default;
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend)
SDV_INTERFACE_ENTRY(sdv::ipc::IConnect)
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
END_SDV_INTERFACE_MAP()
// ---------- IDataSend ----------
/**
* @brief Sends a sequence of buffers via the tunnel.
*
* Prepends a STunnelHeader to the outgoing message buffers and forwards them to the underlying UDS transport.
* In the current implementation, the channel header is always present but not yet used for multiplexing.
*
* @param seqData Sequence of message buffers (may be modified by callee).
* @return true on successful send, false otherwise.
*/
bool SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override;
// ---------- IConnect ----------
/**
* @brief Starts asynchronous connect on the underlying transport and registers this object as receiver.
* @param pReceiver Pointer to callback interface for data and state notifications.
* @return true if connect started, false otherwise.
*/
bool AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver) override;
/**
* @brief Waits until the underlying transport becomes 'connected'.
*
* Forwards to CUnixSocketConnection::WaitForConnection.
* @param uiWaitMs Timeout in milliseconds to wait.
* @return true if connection established, false on timeout or error.
*/
bool WaitForConnection(/*in*/ uint32_t uiWaitMs) override;
/**
* @brief Cancels any pending connect or wait operation.
* Delegated to the underlying transport, if needed.
*/
void CancelWait() override;
/**
* @brief Disconnects the tunnel and underlying transport.
*/
void Disconnect() override;
/**
* @brief Registers a state event callback (forwards to transport).
* @param pEventCallback Pointer to event callback interface.
* @return Registration cookie (nonzero) or 0 on failure.
*/
uint64_t RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
/**
* @brief Unregisters a previously registered state event callback.
* @param uiCookie Registration cookie returned by RegisterStateEventCallback.
*/
void UnregisterStateEventCallback(/*in*/ uint64_t uiCookie) override;
/**
* @brief Gets the current state from the underlying transport.
* @return The current connection state.
*/
sdv::ipc::EConnectState GetConnectState() const override;
// ---------- IObjectDestroy ----------
/**
* @brief Releases and cleans up all resources associated with this object.
*/
void DestroyObject() override;
// ---------- IDataReceiveCallback ----------
/**
* @brief Receives data from the underlying UDS transport.
*
* Expects the first chunk of seqData to be a STunnelHeader, strips and processes it,
* and delivers the remaining payload to the upper-layer receiver registered via AsyncConnect.
*
* @param seqData Sequence of received message buffers (header chunk is removed by this call).
*/
void ReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override;
// ---------- IConnectEventCallback ----------
/**
* @brief Forwards state changes from the underlying UDS transport to the upper layer.
* @param state New connection state.
*/
void SetConnectState(sdv::ipc::EConnectState state) override;
/**
* @brief Assigns a logical channel ID for this connection.
* Optional helper; you may extend with more routing metadata for advanced tunnel use-cases.
* @param channelId The logical channel ID to set.
*/
void SetChannelId(uint16_t channelId);
/**
* @brief Get the logical channel ID for this connection
* @return The channel ID
*/
uint16_t GetChannelId() const noexcept { return m_ChannelId; }
private:
std::shared_ptr<CUnixSocketConnection> m_Transport; ///< shared physical tunnel port
uint16_t m_ChannelId {0}; ///< default logical channel id
sdv::ipc::IDataReceiveCallback* m_pUpperReceiver {nullptr}; ///< Callback to upper layer (data receive)
sdv::ipc::IConnectEventCallback* m_pUpperEvent {nullptr}; ///< Callback to upper layer (state event)
mutable std::mutex m_CallbackMtx; ///< Mutex to guard callback access
};
#endif // UNIX_SOCKET_TUNNEL_CONNECTION_H
#endif // defined(__unix__)

View File

@@ -13,16 +13,22 @@ if(WIN32)
project(uds_win_sockets VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(uds_win_sockets SHARED
"channel_mgnt.h"
"channel_mgnt.cpp"
"connection.h"
"connection.cpp")
add_library(uds_win_sockets STATIC
channel_mgnt.cpp
connection.cpp
)
target_link_libraries(uds_win_sockets ${CMAKE_THREAD_LIBS_INIT} Ws2_32.lib)
target_include_directories(uds_win_sockets
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
./include/
)
target_link_options(uds_win_sockets PRIVATE)
target_include_directories(uds_win_sockets PRIVATE ./include/)
target_link_libraries(uds_win_sockets
PUBLIC
${CMAKE_THREAD_LIBS_INIT}
Ws2_32.lib
)
set_target_properties(uds_win_sockets PROPERTIES PREFIX "")
set_target_properties(uds_win_sockets PROPERTIES SUFFIX ".sdv")

View File

@@ -10,6 +10,7 @@
* Contributors:
* Denisa Ros - initial API and implementation
********************************************************************************/
#ifdef _WIN32
#include "channel_mgnt.h"
#include "connection.h"
@@ -323,15 +324,12 @@ static SOCKET ConnectUnixSocket(
const auto deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(totalTimeoutMs);
int lastError = 0;
while (true)
{
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
lastError = WSAGetLastError();
SDV_LOG_ERROR("[AF_UNIX] socket() FAIL (client), WSA=", lastError);
SDV_LOG_ERROR("[AF_UNIX] socket() FAIL (client), WSA=", WSAGetLastError());
return INVALID_SOCKET;
}
@@ -341,7 +339,7 @@ static SOCKET ConnectUnixSocket(
return s;
}
lastError = WSAGetLastError();
int lastError = WSAGetLastError();
closesocket(s);
if (std::chrono::steady_clock::now() >= deadline)
@@ -364,7 +362,7 @@ bool CSocketsChannelMgnt::OnInitialize()
return true;
}
void CSocketsChannelMgnt::OnServerClosed(const std::string& udsPath, CConnection* ptr)
void CSocketsChannelMgnt::OnServerClosed(const std::string& udsPath, CWinsockConnection* ptr)
{
std::lock_guard<std::mutex> lock(m_udsMtx);
@@ -411,8 +409,8 @@ sdv::ipc::SChannelEndpoint CSocketsChannelMgnt::CreateEndpoint(const sdv::u8stri
return {};
}
// Server-side CConnection, it will accept() a client on first use
auto server = std::make_shared<CConnection>(listenSocket, /*acceptRequired*/ true);
// Server-side CWinsockConnection, it will accept() a client on first use
auto server = std::make_shared<CWinsockConnection>(listenSocket, /*acceptRequired*/ true);
{
std::lock_guard<std::mutex> lock(m_udsMtx);
@@ -481,5 +479,7 @@ sdv::IInterfaceAccess* CSocketsChannelMgnt::Access(const sdv::u8string& cs)
// Client-side connection object (acceptRequired=false)
// Ownership is transferred to the caller (VAPI runtime)
return new CConnection(s, /*acceptRequired*/ false);
}
return new CWinsockConnection(s, /*acceptRequired*/ false);
}
#endif

View File

@@ -10,9 +10,10 @@
* Contributors:
* Denisa Ros - initial API and implementation
********************************************************************************/
#ifdef _WIN32
#ifndef CHANNEL_MGNT_H
#define CHANNEL_MGNT_H
#ifndef WIN_SOCKETS_CHANNEL_MGNT_H
#define WIN_SOCKETS_CHANNEL_MGNT_H
#include <support/component_impl.h>
#include <interfaces/ipc.h>
@@ -150,6 +151,8 @@ public:
DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl")
DECLARE_OBJECT_SINGLETON()
virtual ~CSocketsChannelMgnt() = default;
/**
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
* @return Returns 'true' when the initialization was successful, 'false' when not.
@@ -169,7 +172,7 @@ public:
* The endpoint is implemented as a local AF_UNIX server socket
* (listen socket) on Windows. The connect string has the format:
*
* proto=uds;path=&lt;udsPath&gt;
* proto=uds;path=<udsPath>;
*
* If no configuration is provided or no path is specified, a default
* path is used (under %LOCALAPPDATA%/sdv)
@@ -193,7 +196,7 @@ public:
* Overload of sdv::ipc::IChannelAccess::Access
*
* The connect string is expected to contain:
* proto=uds;path=&lt;udsPath&gt;
* proto=uds;path=<udsPath>;
*
* For the first Access() call with a given path, the server-side
* connection object created by CreateEndpoint() can be returned.
@@ -206,18 +209,17 @@ public:
sdv::IInterfaceAccess* Access(const sdv::u8string& ssConnectString) override;
/**
* @brief Called by a CConnection instance when the server side is closed
* @brief Called by a CWinsockConnection instance when the server side is closed
*
* Used to clean up internal registries for a given UDS path
*
* @param udsPath Path to the UDS endpoint
* @param ptr Pointer to the CConnection instance that was closed
* @param ptr Pointer to the CWinsockConnection instance that was closed
*/
void OnServerClosed(const std::string& udsPath, CConnection* ptr);
void OnServerClosed(const std::string& udsPath, CWinsockConnection* ptr);
private:
/// @brief Registry of AF_UNIX server connections keyed by normalized UDS path
std::map<std::string, std::shared_ptr<CConnection>> m_udsServers;
std::map<std::string, std::shared_ptr<CWinsockConnection>> m_udsServers;
/**
* @brief Set of UDS paths that already returned their server-side
@@ -234,4 +236,5 @@ private:
// SDV object factory macro
DEFINE_SDV_OBJECT(CSocketsChannelMgnt)
#endif // ! defined CHANNEL_MGNT_H
#endif // ! defined WIN_SOCKETS_CHANNEL_MGNT_H
#endif

View File

@@ -10,7 +10,7 @@
* Contributors:
* Denisa Ros - initial API and implementation
********************************************************************************/
#ifdef _WIN32
#include "connection.h"
#include <numeric>
@@ -18,8 +18,8 @@
#include <cstring>
#include <chrono>
CConnection::CConnection(SOCKET preconfiguredSocket, bool acceptConnectionRequired)
: m_ConnectionStatus(sdv::ipc::EConnectStatus::uninitialized)
CWinsockConnection::CWinsockConnection(unsigned long long preconfiguredSocket, bool acceptConnectionRequired)
: m_ConnectionState(sdv::ipc::EConnectState::uninitialized)
, m_AcceptConnectionRequired(acceptConnectionRequired)
, m_CancelWait(false)
{
@@ -30,23 +30,34 @@ CConnection::CConnection(SOCKET preconfiguredSocket, bool acceptConnectionRequir
if (m_AcceptConnectionRequired)
{
// Server side: we own a listening socket, active socket is not yet accepted
m_ListenSocket = preconfiguredSocket;
m_ListenSocket = static_cast<SOCKET>(preconfiguredSocket);
m_ConnectionSocket = INVALID_SOCKET;
}
else
{
// Client side: we already have a connected socket
m_ListenSocket = INVALID_SOCKET;
m_ConnectionSocket = preconfiguredSocket;
m_ConnectionSocket = static_cast<SOCKET>(preconfiguredSocket);
}
}
CConnection::~CConnection()
/*CWinsockConnection::CWinsockConnection()
: m_ConnectionState(sdv::ipc::EConnectState::uninitialized)
, m_AcceptConnectionRequired(false)
, m_CancelWait(false)
{
m_ListenSocket = INVALID_SOCKET;
m_ConnectionSocket = INVALID_SOCKET;
std::fill(std::begin(m_SendBuffer), std::end(m_SendBuffer), '\0');
std::fill(std::begin(m_ReceiveBuffer), std::end(m_ReceiveBuffer), '\0');
}*/
CWinsockConnection::~CWinsockConnection()
{
try
{
StopThreadsAndCloseSockets();
m_ConnectionStatus = sdv::ipc::EConnectStatus::disconnected;
m_ConnectionState = sdv::ipc::EConnectState::disconnected;
}
catch (...)
{
@@ -54,11 +65,11 @@ CConnection::~CConnection()
}
}
void CConnection::SetStatus(sdv::ipc::EConnectStatus status)
void CWinsockConnection::SetConnectState(sdv::ipc::EConnectState state)
{
{
std::lock_guard<std::mutex> lk(m_MtxConnect);
m_ConnectionStatus.store(status, std::memory_order_release);
m_ConnectionState.store(state, std::memory_order_release);
}
// Wake up any waiter
@@ -69,7 +80,7 @@ void CConnection::SetStatus(sdv::ipc::EConnectStatus status)
{
try
{
m_pEvent->SetStatus(status);
m_pEvent->SetConnectState(state);
}
catch (...)
{
@@ -78,7 +89,7 @@ void CConnection::SetStatus(sdv::ipc::EConnectStatus status)
}
}
int32_t CConnection::Send(const char* data, int32_t dataLength)
int32_t CWinsockConnection::Send(const char* data, int32_t dataLength)
{
int32_t total = 0;
@@ -88,7 +99,7 @@ int32_t CConnection::Send(const char* data, int32_t dataLength)
if (n == SOCKET_ERROR)
{
SDV_LOG_ERROR("send failed with error: ", std::to_string(WSAGetLastError()));
m_ConnectionStatus = sdv::ipc::EConnectStatus::communication_error;
m_ConnectionState = sdv::ipc::EConnectState::communication_error;
m_ConnectionSocket = INVALID_SOCKET;
return (total > 0) ? total : SOCKET_ERROR;
}
@@ -98,7 +109,7 @@ int32_t CConnection::Send(const char* data, int32_t dataLength)
return total;
}
int CConnection::SendExact(const char* data, int len)
int CWinsockConnection::SendExact(const char* data, int len)
{
int total = 0;
@@ -115,13 +126,13 @@ int CConnection::SendExact(const char* data, int len)
return total;
}
bool CConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
bool CWinsockConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
{
// Must be connected and have a valid socket
if (m_ConnectionStatus != sdv::ipc::EConnectStatus::connected ||
if (m_ConnectionState != sdv::ipc::EConnectState::connected ||
m_ConnectionSocket == INVALID_SOCKET)
{
m_ConnectionStatus = sdv::ipc::EConnectStatus::communication_error;
m_ConnectionState = sdv::ipc::EConnectState::communication_error;
return false;
}
@@ -288,11 +299,11 @@ bool CConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqDa
return (sentOffset == required);
}
SOCKET CConnection::AcceptConnection()
SOCKET CWinsockConnection::AcceptConnection()
{
if (m_ListenSocket == INVALID_SOCKET)
{
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return INVALID_SOCKET;
}
@@ -310,7 +321,7 @@ SOCKET CConnection::AcceptConnection()
if (sr == SOCKET_ERROR)
{
SDV_LOG_ERROR("[AF_UNIX] select(listen) FAIL, WSA=", WSAGetLastError());
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return INVALID_SOCKET;
}
@@ -329,7 +340,7 @@ SOCKET CConnection::AcceptConnection()
}
SDV_LOG_ERROR("[AF_UNIX] accept FAIL, WSA=", err);
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return INVALID_SOCKET;
}
@@ -338,11 +349,11 @@ SOCKET CConnection::AcceptConnection()
}
SDV_LOG_ERROR("[AF_UNIX] accept canceled (stop flag)");
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return INVALID_SOCKET;
}
bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
bool CWinsockConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
{
// Store callbacks
m_pReceiver = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IDataReceiveCallback>();
@@ -360,14 +371,14 @@ bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
m_ConnectThread.join();
// Start the connect worker
m_ConnectThread = std::thread(&CConnection::ConnectWorker, this);
m_ConnectThread = std::thread(&CWinsockConnection::ConnectWorker, this);
return true;
}
bool CConnection::WaitForConnection(uint32_t uiWaitMs)
bool CWinsockConnection::WaitForConnection(uint32_t uiWaitMs)
{
if (m_ConnectionStatus.load(std::memory_order_acquire) ==
sdv::ipc::EConnectStatus::connected)
if (m_ConnectionState.load(std::memory_order_acquire) ==
sdv::ipc::EConnectState::connected)
{
return true;
}
@@ -380,16 +391,16 @@ bool CConnection::WaitForConnection(uint32_t uiWaitMs)
lk,
[this]
{
return m_ConnectionStatus.load(std::memory_order_acquire) ==
sdv::ipc::EConnectStatus::connected;
return m_ConnectionState.load(std::memory_order_acquire) ==
sdv::ipc::EConnectState::connected;
});
return true;
}
if (uiWaitMs == 0u) // zero wait
{
return (m_ConnectionStatus.load(std::memory_order_acquire) ==
sdv::ipc::EConnectStatus::connected);
return (m_ConnectionState.load(std::memory_order_acquire) ==
sdv::ipc::EConnectState::connected);
}
// finite wait
@@ -398,24 +409,24 @@ bool CConnection::WaitForConnection(uint32_t uiWaitMs)
std::chrono::milliseconds(uiWaitMs),
[this]
{
return m_ConnectionStatus.load(std::memory_order_acquire) ==
sdv::ipc::EConnectStatus::connected;
return m_ConnectionState.load(std::memory_order_acquire) ==
sdv::ipc::EConnectState::connected;
});
}
void CConnection::CancelWait()
void CWinsockConnection::CancelWait()
{
m_CancelWait.store(true);
}
void CConnection::Disconnect()
void CWinsockConnection::Disconnect()
{
m_CancelWait.store(true);
StopThreadsAndCloseSockets();
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
}
uint64_t CConnection::RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback)
uint64_t CWinsockConnection::RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback)
{
// Extract IConnectEventCallback interface
m_pEvent = sdv::TInterfaceAccessPtr(pEventCallback).GetInterface<sdv::ipc::IConnectEventCallback>();
@@ -424,7 +435,7 @@ uint64_t CConnection::RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess*
return (m_pEvent != nullptr) ? 1ULL : 0ULL;
}
void CConnection::UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie)
void CWinsockConnection::UnregisterStateEventCallback(/*in*/ uint64_t uiCookie)
{
// Only one callback supported -> cookie value is 1
if (uiCookie == 1ULL)
@@ -433,21 +444,21 @@ void CConnection::UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie)
}
}
sdv::ipc::EConnectStatus CConnection::GetStatus() const
sdv::ipc::EConnectState CWinsockConnection::GetConnectState() const
{
return m_ConnectionStatus;
return m_ConnectionState;
}
void CConnection::DestroyObject()
void CWinsockConnection::DestroyObject()
{
m_StopReceiveThread = true;
m_StopConnectThread = true;
StopThreadsAndCloseSockets();
m_ConnectionStatus = sdv::ipc::EConnectStatus::disconnected;
m_ConnectionState = sdv::ipc::EConnectState::disconnected;
}
void CConnection::ConnectWorker()
void CWinsockConnection::ConnectWorker()
{
try
{
@@ -456,18 +467,18 @@ void CConnection::ConnectWorker()
// SERVER SIDE
if (m_ListenSocket == INVALID_SOCKET)
{
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
SetStatus(sdv::ipc::EConnectStatus::initializing);
SetConnectState(sdv::ipc::EConnectState::initializing);
SDV_LOG_INFO("[AF_UNIX] Srv ConnectWorker start: listen=%llu",
static_cast<uint64_t>(m_ListenSocket));
SOCKET c = AcceptConnection();
SDV_LOG_INFO("[AF_UNIX] Srv AcceptConnection returned: sock=%llu status=%d",
SDV_LOG_INFO("[AF_UNIX] Srv AcceptConnection returned: sock=%llu state=%d",
static_cast<uint64_t>(c),
static_cast<int>(m_ConnectionStatus.load()));
static_cast<int>(m_ConnectionState.load()));
if (c == INVALID_SOCKET)
{
@@ -475,7 +486,7 @@ void CConnection::ConnectWorker()
{
try
{
m_pEvent->SetStatus(m_ConnectionStatus);
m_pEvent->SetConnectState(m_ConnectionState);
}
catch (...)
{
@@ -485,7 +496,7 @@ void CConnection::ConnectWorker()
}
m_ConnectionSocket = c;
SetStatus(sdv::ipc::EConnectStatus::connected);
SetConnectState(sdv::ipc::EConnectState::connected);
StartReceiveThread_Unsafe();
return;
}
@@ -494,22 +505,22 @@ void CConnection::ConnectWorker()
// CLIENT SIDE
if (m_ConnectionSocket == INVALID_SOCKET)
{
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
}
// Client side: socket is already connected
SetStatus(sdv::ipc::EConnectStatus::connected);
SetConnectState(sdv::ipc::EConnectState::connected);
StartReceiveThread_Unsafe();
}
catch (...)
{
SetStatus(sdv::ipc::EConnectStatus::connection_error);
SetConnectState(sdv::ipc::EConnectState::connection_error);
}
}
void CConnection::StartReceiveThread_Unsafe()
void CWinsockConnection::StartReceiveThread_Unsafe()
{
if (m_ReceiveThread.joinable())
{
@@ -517,10 +528,10 @@ void CConnection::StartReceiveThread_Unsafe()
}
m_StopReceiveThread.store(false);
m_ReceiveThread = std::thread(&CConnection::ReceiveMessages, this);
m_ReceiveThread = std::thread(&CWinsockConnection::ReceiveMessages, this);
}
void CConnection::StopThreadsAndCloseSockets()
void CWinsockConnection::StopThreadsAndCloseSockets()
{
// Signal stop
m_StopReceiveThread.store(true);
@@ -574,7 +585,7 @@ void CConnection::StopThreadsAndCloseSockets()
static_cast<uint64_t>(s));
}
bool CConnection::ReadNumberOfBytes(char* buffer, uint32_t length)
bool CWinsockConnection::ReadNumberOfBytes(char* buffer, uint32_t length)
{
uint32_t received = 0;
@@ -607,7 +618,7 @@ bool CConnection::ReadNumberOfBytes(char* buffer, uint32_t length)
return (received == length);
}
bool CConnection::ValidateHeader(const SMsgHeader& msgHeader)
bool CWinsockConnection::ValidateHeader(const SMsgHeader& msgHeader)
{
// Kept only for compatibility with any legacy users (not used in SDV path)
if ((msgHeader.msgStart == m_MsgStart) && (msgHeader.msgEnd == m_MsgEnd))
@@ -618,7 +629,7 @@ bool CConnection::ValidateHeader(const SMsgHeader& msgHeader)
}
uint32_t CConnection::ReadDataTable(const CMessage& message, SDataContext& dataCtx)
uint32_t CWinsockConnection::ReadDataTable(const CMessage& message, SDataContext& dataCtx)
{
uint32_t offset = 0;
@@ -691,7 +702,7 @@ uint32_t CConnection::ReadDataTable(const CMessage& message, SDataContext& dataC
return offset;
}
bool CConnection::ReadDataChunk(const CMessage& message, uint32_t offset, SDataContext& dataCtx)
bool CWinsockConnection::ReadDataChunk(const CMessage& message, uint32_t offset, SDataContext& dataCtx)
{
if (offset < sizeof(SMsgHdr))
return false;
@@ -743,12 +754,12 @@ bool CConnection::ReadDataChunk(const CMessage& message, uint32_t offset, SDataC
return true;
}
void CConnection::ReceiveSyncRequest(const CMessage& message)
void CWinsockConnection::ReceiveSyncRequest(const CMessage& message)
{
const auto hdr = message.GetMsgHdr();
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
@@ -764,12 +775,12 @@ void CConnection::ReceiveSyncRequest(const CMessage& message)
return;
}
void CConnection::ReceiveSyncAnswer(const CMessage& message)
void CWinsockConnection::ReceiveSyncAnswer(const CMessage& message)
{
const auto hdr = message.GetMsgHdr();
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
@@ -786,12 +797,12 @@ void CConnection::ReceiveSyncAnswer(const CMessage& message)
return;
}
void CConnection::ReceiveConnectRequest(const CMessage& message)
void CWinsockConnection::ReceiveConnectRequest(const CMessage& message)
{
const auto hdr = message.GetConnectHdr();
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
@@ -808,42 +819,42 @@ void CConnection::ReceiveConnectRequest(const CMessage& message)
return;
}
void CConnection::ReceiveConnectAnswer(const CMessage& message)
void CWinsockConnection::ReceiveConnectAnswer(const CMessage& message)
{
const auto hdr = message.GetConnectHdr();
if (hdr.uiVersion != SDVFrameworkInterfaceVersion)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
// Handshake complete (client side)
SetStatus(sdv::ipc::EConnectStatus::connected);
SetConnectState(sdv::ipc::EConnectState::connected);
}
void CConnection::ReceiveConnectTerm(const CMessage& /*message*/)
void CWinsockConnection::ReceiveConnectTerm(const CMessage& /*message*/)
{
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
m_StopReceiveThread.store(true);
}
void CConnection::ReceiveDataMessage(const CMessage& message, SDataContext& dataCtx)
void CWinsockConnection::ReceiveDataMessage(const CMessage& message, SDataContext& dataCtx)
{
const uint32_t payloadOffset = ReadDataTable(message, dataCtx);
if (payloadOffset == 0)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
if (!ReadDataChunk(message, payloadOffset, dataCtx))
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
}
void CConnection::ReceiveDataFragmentMessage(const CMessage& message, SDataContext& dataCtx)
void CWinsockConnection::ReceiveDataFragmentMessage(const CMessage& message, SDataContext& dataCtx)
{
uint32_t offset = static_cast<uint32_t>(sizeof(SFragmentedMsgHdr));
@@ -852,19 +863,19 @@ void CConnection::ReceiveDataFragmentMessage(const CMessage& message, SDataConte
offset = ReadDataTable(message, dataCtx);
if (offset == 0)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
}
if (!ReadDataChunk(message, offset, dataCtx))
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
return;
}
}
void CConnection::ReceiveMessages()
void CWinsockConnection::ReceiveMessages()
{
try
{
@@ -898,14 +909,14 @@ void CConnection::ReceiveMessages()
if (!ReadNumberOfBytes(reinterpret_cast<char*>(&packetSize),
sizeof(packetSize)))
{
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
SDV_LOG_WARNING("[UDS][RX] Incomplete payload read -> disconnected");
break;
}
if (packetSize == 0 || packetSize > kMaxUdsPacketSize)
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
break;
}
@@ -914,7 +925,7 @@ void CConnection::ReceiveMessages()
if (!ReadNumberOfBytes(reinterpret_cast<char*>(payload.data()),
packetSize))
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
SDV_LOG_WARNING("[UDS][RX] Invalid SDV message (envelope)");
break;
}
@@ -923,7 +934,7 @@ void CConnection::ReceiveMessages()
CMessage msg(std::move(payload));
if (!msg.IsValid())
{
SetStatus(sdv::ipc::EConnectStatus::communication_error);
SetConnectState(sdv::ipc::EConnectState::communication_error);
continue;
}
@@ -941,12 +952,13 @@ void CConnection::ReceiveMessages()
break;
}
if (m_ConnectionStatus == sdv::ipc::EConnectStatus::terminating)
if (m_ConnectionState == sdv::ipc::EConnectState::terminating)
break;
}
}
catch (...)
{
SetStatus(sdv::ipc::EConnectStatus::disconnected);
SetConnectState(sdv::ipc::EConnectState::disconnected);
}
}
}
#endif

View File

@@ -10,9 +10,9 @@
* Contributors:
* Denisa Ros - initial API and implementation
********************************************************************************/
#ifndef CONNECTION_H
#define CONNECTION_H
#ifdef _WIN32
#ifndef WIN_SOCKETS_CONNECTION_H
#define WIN_SOCKETS_CONNECTION_H
#include <interfaces/ipc.h>
#include <support/interface_ptr.h>
@@ -35,6 +35,7 @@
# endif
#endif
/// @brief Legacy framing markers for the old message header (not used by SDV envelope)
constexpr uint32_t m_MsgStart = 0x01020304; ///< Value to mark the start of the legacy message header
constexpr uint32_t m_MsgEnd = 0x05060708; ///< Value to mark the end of the legacy message header
@@ -67,10 +68,10 @@ struct SMsgHeader
*
* Exposes:
* - sdv::ipc::IDataSend : sending SDV-framed messages
* - sdv::ipc::IConnect : async connect / wait / status / events
* - sdv::ipc::IConnect : async connect / wait / state / events
* - sdv::IObjectDestroy : explicit destruction hook for SDV runtime
*/
class CConnection
class CWinsockConnection
: public sdv::IInterfaceAccess
, public sdv::ipc::IDataSend
, public sdv::ipc::IConnect
@@ -80,19 +81,19 @@ public:
/**
* @brief default constructor used by create endpoint - allocates new buffers m_Sender and m_Receiver
*/
CConnection();
CWinsockConnection();
/**
* @brief access existing connection
* @param[in] preconfiguredSocket Prepared socket for the connection
* @param[in] acceptConnectionRequired If true connection has to be accepted before receive thread can be started
*/
CConnection(SOCKET preconfiguredSocket, bool acceptConnectionRequired);
CWinsockConnection(unsigned long long preconfiguredSocket, bool acceptConnectionRequired);
/**
* @brief Virtual destructor needed for "delete this;"
*/
virtual ~CConnection();
virtual ~CWinsockConnection();
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend)
@@ -122,7 +123,7 @@ public:
* @param[in] pReceiver Object that exposes IDataReceiveCallback and
* optionally IConnectEventCallback
* @return true when the connect worker was started successfully
* Use GetStatus / WaitForConnection / callbacks for status
* Use GetConnectState / WaitForConnection / callbacks for state
*/
bool AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver) override;
@@ -132,7 +133,7 @@ public:
* Overload of sdv::ipc::IConnect::WaitForConnection
*
* @param[in] uiWaitMs
* - 0 : do not wait, just check current status
* - 0 : do not wait, just check current state
* - 0xFFFFFFFF: wait indefinitely
* - otherwise : wait up to uiWaitMs milliseconds
* @return true if the connection is in connected state when returning
@@ -149,36 +150,36 @@ public:
/**
* @brief Disconnect from the peer and stop I/O threads
*
* This sets the status to disconnected and releases the event interface
* This sets the state to disconnected and releases the event interface
*/
void Disconnect() override;
/**
* @brief Register event callback interface for connection status updates
* @brief Register event callback interface for connection state updates
*
* Overload of sdv::ipc::IConnect::RegisterStatusEventCallback
* Overload of sdv::ipc::IConnect::RegisterStateEventCallback
*
* @param[in] pEventCallback Pointer to an object exposing
* sdv::ipc::IConnectEventCallback.
* @return A registration cookie (1 = valid, 0 = failure)
*/
uint64_t RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
uint64_t RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
/**
* @brief Unregister the status event callback using its cookie
* @brief Unregister the state event callback using its cookie
*
* Overload of sdv::ipc::IConnect::UnregisterStatusEventCallback
* Overload of sdv::ipc::IConnect::UnregisterStateEventCallback
*
* @param[in] uiCookie Cookie returned by RegisterStatusEventCallback
* @param[in] uiCookie Cookie returned by RegisterStateEventCallback
*/
void UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie) override;
void UnregisterStateEventCallback(/*in*/ uint64_t uiCookie) override;
/**
* @brief Get current status of the connection
* @brief Get current state of the connection
*
* @return Current sdv::ipc::EConnectStatus
* @return Current sdv::ipc::EConnectState
*/
sdv::ipc::EConnectStatus GetStatus() const override;
sdv::ipc::EConnectState GetConnectState() const override;
/**
* @brief Destroy the object
@@ -196,7 +197,7 @@ private:
std::thread m_ConnectThread;
std::atomic<bool> m_StopReceiveThread{false}; ///< bool variable to stop thread
std::atomic<bool> m_StopConnectThread{false};
std::atomic<sdv::ipc::EConnectStatus> m_ConnectionStatus; ///< the status of the connection
std::atomic<sdv::ipc::EConnectState> m_ConnectionState; ///< the state of the connection
sdv::ipc::IDataReceiveCallback* m_pReceiver = nullptr; ///< Receiver to pass the messages if available
sdv::ipc::IConnectEventCallback* m_pEvent = nullptr; ///< Event receiver
@@ -217,7 +218,7 @@ private:
/// @brief Server accept loop / client connect confirmation
void ConnectWorker();
/// @brief Start the RX thread (pre: status=connected, socket valid)
/// @brief Start the RX thread (pre: state=connected, socket valid)
void StartReceiveThread_Unsafe();
/// @brief Stop worker threads and close all sockets cleanly
@@ -451,7 +452,7 @@ private:
* @brief Handle an incoming connect_term message
*
* Indicates that the peer requests immediate termination of the connection
* Sets status to disconnected and stops the RX loop
* Sets state to disconnected and stops the RX loop
*
* @param message SDV envelope containing the connect_term header
*/
@@ -503,8 +504,9 @@ private:
*/
bool ReadDataChunk(const CMessage& rMessage, uint32_t uiOffset, SDataContext& rsDataCtxt);
/// @brief Centralized status update (notifies waiters and callbacks)
void SetStatus(sdv::ipc::EConnectStatus status);
/// @brief Centralized state update (notifies waiters and callbacks)
void SetConnectState(sdv::ipc::EConnectState state);
};
#endif // CONNECTION_H
#endif // WIN_SOCKETS_CONNECTION_H
#endif

View File

@@ -0,0 +1,40 @@
#*******************************************************************************
# 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
#*******************************************************************************
# Define project
project(uds_win_tunnel VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(uds_win_tunnel STATIC
channel_mgnt.cpp
connection.cpp
)
target_link_libraries(uds_win_tunnel
PRIVATE
uds_win_sockets
Ws2_32.lib
${CMAKE_THREAD_LIBS_INIT}
)
target_include_directories(uds_win_tunnel
PRIVATE
./include/
../uds_win_sockets/
)
set_target_properties(uds_win_tunnel PROPERTIES PREFIX "")
set_target_properties(uds_win_tunnel PROPERTIES SUFFIX ".sdv")
# Build dependencies
add_dependencies(uds_win_tunnel CompileCoreIDL)
# Appending the service in the service list
set(SDV_Service_List ${SDV_Service_List} uds_win_tunnel PARENT_SCOPE)

View File

@@ -0,0 +1,414 @@
/********************************************************************************
* 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
********************************************************************************/
#ifdef _WIN32
#include "channel_mgnt.h"
#include "../../global/base64.h"
#include <support/toml.h>
#include <interfaces/process.h>
#include <future>
#pragma push_macro("interface")
#undef interface
#pragma push_macro("GetObject")
#undef GetObject
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <WinSock2.h>
#include <Windows.h>
#include <ws2tcpip.h>
#include <afunix.h>
#include <array>
#pragma pop_macro("GetObject")
#pragma pop_macro("interface")
extern int StartUpWinSock();
namespace
{
/**
* @brief Parse a tunnel connect/config string and extract the path.
*
* Expected format:
* "proto=tunnel;path=<something>;"
*
* Behavior:
* - If "proto=tunnel" missing -> false
* - If "path=" missing -> true and outPath.clear()
*
* @param[in] cs The connect/config string to parse.
* @param[out] outPath The extracted path, or empty if not found.
* @return true if parsing succeeded, false otherwise.
*/
static bool ParseTunnelPath(const std::string& cs, std::string& outPath)
{
constexpr const char* protoKey = "proto=tunnel";
constexpr const char* pathKey = "path=";
if (cs.find(protoKey) == std::string::npos)
{
return false;
}
const auto p = cs.find(pathKey);
if (p == std::string::npos)
{
outPath.clear();
return true;
}
const auto start = p + std::strlen(pathKey);
const auto end = cs.find(';', start);
if (end == std::string::npos)
{
outPath = cs.substr(start);
}
else
{
outPath = cs.substr(start, end - start);
}
return true;
}
/**
* @brief Expands Windows environment variables in a string (e.g., %TEMP%).
* @param[in] in Input string possibly containing environment variables.
* @return String with environment variables expanded, or original if expansion fails.
*/
static std::string ExpandEnvVars(const std::string& in)
{
if (in.find('%') == std::string::npos)
{
return in;
}
char buf[4096] = {};
DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, static_cast<DWORD>(sizeof(buf)));
if (n > 0 && n < sizeof(buf))
{
return std::string(buf);
}
return in;
}
/**
* @brief Clamps a UDS path to the maximum allowed by SOCKADDR_UN.
* @param[in] p The input path.
* @return The clamped path.
*/
static std::string ClampUdsPath(const std::string& p)
{
SOCKADDR_UN tmp{};
constexpr auto kMax = sizeof(tmp.sun_path) - 1;
if (p.size() <= kMax)
{
return p;
}
return p.substr(0, kMax);
}
// Only for logging basename
/**
* @brief Normalizes a raw UDS path for Windows, extracting the basename and ensuring a default if empty.
* @param[in] raw The raw path string.
* @return The normalized basename, clamped to max length.
*/
static std::string NormalizeUdsPathForWindows(const std::string& raw)
{
std::string p = ExpandEnvVars(raw);
const size_t pos = p.find_last_of("/\\");
std::string base = (pos == std::string::npos) ? p : p.substr(pos + 1);
if (base.empty())
{
base = "sdv_tunnel.sock";
}
SDV_LOG_INFO("[AF_UNIX][Tunnel] Normalize raw='", raw, "' -> base='", base, "'");
return ClampUdsPath(base);
}
/**
* @brief Creates a short, safe UDS path in the Windows temp directory.
* @param[in] raw The raw path string.
* @return The full path in %TEMP%\sdv\, clamped to max length.
*/
static std::string MakeShortWinUdsPath(const std::string& raw)
{
std::string p = ExpandEnvVars(raw);
const size_t pos = p.find_last_of("/\\");
std::string base = (pos == std::string::npos) ? p : p.substr(pos + 1);
if (base.empty())
{
base = "sdv_tunnel.sock";
}
std::string dir = ExpandEnvVars("%TEMP%\\sdv\\");
CreateDirectoryA(dir.c_str(), nullptr);
const std::string full = dir + base;
return ClampUdsPath(full);
}
/**
* @brief Creates an AF_UNIX listen socket at the specified path.
* @param[in] rawPath The raw path for the socket.
* @return The created socket handle, or INVALID_SOCKET on failure.
*/
static SOCKET CreateUnixListenSocket(const std::string& rawPath)
{
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] socket() FAIL (listen), WSA=", WSAGetLastError());
return INVALID_SOCKET;
}
std::string udsPath = MakeShortWinUdsPath(rawPath);
SOCKADDR_UN addr{};
addr.sun_family = AF_UNIX;
strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str());
const int addrlen = static_cast<int>(
offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1);
::remove(udsPath.c_str());
if (bind(s, reinterpret_cast<sockaddr*>(&addr), addrlen) == SOCKET_ERROR)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] bind FAIL, WSA=",
WSAGetLastError(), ", path='", udsPath, "'");
closesocket(s);
return INVALID_SOCKET;
}
if (listen(s, SOMAXCONN) == SOCKET_ERROR)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] listen FAIL, WSA=",
WSAGetLastError(), ", path='", udsPath, "'");
closesocket(s);
return INVALID_SOCKET;
}
SDV_LOG_INFO("[AF_UNIX][Tunnel] bind+listen OK, path='", udsPath, "'");
return s;
}
/**
* @brief Connects to an AF_UNIX socket at the specified path, retrying until timeout.
* @param[in] rawPath The raw path to connect to.
* @param[in] totalTimeoutMs Total timeout in milliseconds.
* @param[in] retryDelayMs Delay between retries in milliseconds.
* @return The connected socket handle, or INVALID_SOCKET on failure.
*/
static SOCKET ConnectUnixSocket(
const std::string& rawPath,
uint32_t totalTimeoutMs,
uint32_t retryDelayMs)
{
const std::string udsPath = MakeShortWinUdsPath(rawPath);
SOCKADDR_UN addr{};
addr.sun_family = AF_UNIX;
strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str());
const int addrlen = static_cast<int>(
offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1);
const auto deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(totalTimeoutMs);
int lastError = 0;
while (true)
{
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
lastError = WSAGetLastError();
SDV_LOG_ERROR("[AF_UNIX][Tunnel] socket() FAIL (client), WSA=", lastError);
return INVALID_SOCKET;
}
if (connect(s, reinterpret_cast<const sockaddr*>(&addr), addrlen) == 0)
{
SDV_LOG_INFO("[AF_UNIX][Tunnel] connect OK, path='", udsPath, "'");
return s;
}
lastError = WSAGetLastError();
closesocket(s);
if (std::chrono::steady_clock::now() >= deadline)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] connect TIMEOUT, last WSA=",
lastError, ", path='", udsPath, "'");
return INVALID_SOCKET;
}
std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
}
}
} // anonymous namespace
bool CSocketsTunnelChannelMgnt::OnInitialize()
{
return true;
}
void CSocketsTunnelChannelMgnt::OnShutdown()
{}
// -------- Server bookkeeping (optional) --------
void CSocketsTunnelChannelMgnt::OnServerClosed(const std::string& udsPath, CWinTunnelConnection* ptr)
{
std::lock_guard<std::mutex> lock(m_udsMtx);
auto it = m_udsServers.find(udsPath);
if (it != m_udsServers.end() && it->second.get() == ptr)
{
m_udsServers.erase(it);
}
m_udsServerClaimed.erase(udsPath);
}
// -------- ICreateEndpoint --------
sdv::ipc::SChannelEndpoint CSocketsTunnelChannelMgnt::CreateEndpoint(const sdv::u8string& cfgStr)
{
sdv::ipc::SChannelEndpoint ep{};
if (StartUpWinSock() != 0)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] WinSock startup failed in CreateEndpoint");
return ep;
}
// Optional TOML config: [IpcChannel] Path = "..."
std::string udsRaw;
if (!cfgStr.empty())
{
//for toml file
bool isTOML = cfgStr.find('=') == std::string::npos;
if(isTOML)
{
sdv::toml::CTOMLParser cfg(cfgStr.c_str());
auto pathNode = cfg.GetDirect("IpcChannel.Path");
if (pathNode.GetType() == sdv::toml::ENodeType::node_string)
{
udsRaw = static_cast<std::string>(pathNode.GetValue());
}
}
//for connect string
if (udsRaw.empty())
{
const std::string s(cfgStr);
const std::string key = "path=";
auto pos = s.find(key);
if (pos != std::string::npos)
{
auto end = s.find(';', pos + key.size());
if (end == std::string::npos)
udsRaw = s.substr(pos + key.size());
else
udsRaw = s.substr(pos + key.size(), end - pos - key.size());
}
}
}
if (udsRaw.empty())
{
udsRaw = "%LOCALAPPDATA%/sdv/tunnel.sock";
}
std::string udsPathBase = NormalizeUdsPathForWindows(udsRaw);
SDV_LOG_INFO("[AF_UNIX][Tunnel] endpoint udsPath=", udsPathBase);
SOCKET listenSocket = CreateUnixListenSocket(udsPathBase);
if (listenSocket == INVALID_SOCKET)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] Failed to create listen socket for endpoint: ", udsPathBase);
return ep;
}
auto serverTransport = std::make_shared<CWinsockConnection>( static_cast<unsigned long long>(listenSocket), true);
auto serverTunnel = std::make_shared<CWinTunnelConnection>(serverTransport, /*channelId*/ static_cast<uint16_t>(0u));
{
std::lock_guard<std::mutex> lock(m_udsMtx);
m_udsServers[udsPathBase] = serverTunnel;
m_udsServerClaimed.erase(udsPathBase);
}
ep.pConnection = static_cast<sdv::IInterfaceAccess*>(serverTunnel.get());
ep.ssConnectString = "proto=tunnel;role=server;path=" + udsPathBase + ";";
return ep;
}
sdv::IInterfaceAccess* CSocketsTunnelChannelMgnt::Access(const sdv::u8string& cs)
{
if (StartUpWinSock() != 0)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] WinSock startup failed in Access()" );
return nullptr;
}
std::string connectStr = static_cast<std::string>(cs);
std::string udsRaw;
if (!ParseTunnelPath(connectStr, udsRaw))
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] Invalid tunnel connect string: ", connectStr);
return nullptr;
}
if (udsRaw.empty())
{
udsRaw = "%LOCALAPPDATA%/sdv/tunnel.sock";
}
std::string udsPathBase = NormalizeUdsPathForWindows(udsRaw);
SDV_LOG_INFO("[AF_UNIX][Tunnel] Access udsPath=", udsPathBase);
const bool isServer =
(connectStr.find("role=server") != std::string::npos);
{
std::lock_guard<std::mutex> lock(m_udsMtx);
auto it = m_udsServers.find(udsPathBase);
if (isServer && it != m_udsServers.end() && it->second != nullptr)
{
if (!m_udsServerClaimed.count(udsPathBase))
{
m_udsServerClaimed.insert(udsPathBase);
SDV_LOG_INFO("[AF_UNIX][Tunnel] Access -> RETURN SERVER for ", udsPathBase);
return it->second.get(); // Ownership: managed by m_udsServers (do not delete)
}
}
}
// CLIENT: create AF_UNIX client socket and wrap it in a tunnel
SOCKET s = ConnectUnixSocket(udsPathBase, /*totalTimeoutMs*/ 5000, /*retryDelayMs*/ 50);
if (s == INVALID_SOCKET)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] Failed to connect client socket for ", udsPathBase);
return nullptr;
}
SDV_LOG_INFO("[AF_UNIX][Tunnel] Access -> CREATE CLIENT for ", udsPathBase);
auto clientTransport = std::make_shared<CWinsockConnection>(s, /*acceptRequired*/ false);
// Ownership: The returned pointer must be managed and deleted by the SDV framework via IObjectDestroy
auto* tunnelClient = new CWinTunnelConnection(clientTransport, /*channelId*/ 0u);
return static_cast<sdv::IInterfaceAccess*>(tunnelClient);
}
#endif

View File

@@ -0,0 +1,106 @@
/********************************************************************************
* 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
********************************************************************************/
#ifdef _WIN32
#ifndef WIN_TUNNEL_CHANNEL_MGNT_H
#define WIN_TUNNEL_CHANNEL_MGNT_H
#include <support/component_impl.h>
#include <interfaces/ipc.h>
#include "../sdv_services/uds_win_sockets/channel_mgnt.h"
#include "../sdv_services/uds_win_sockets/connection.h"
#include "connection.h"
#include <mutex>
#include <map>
#include <memory>
#include <set>
#include <string>
// Winsock headers are required for SOCKET / AF_UNIX / WSAStartup
// NOTE: The actual initialization is done via StartUpWinSock()
#include <ws2tcpip.h>
class CWinTunnelConnection;
/**
* @class CSocketsTunnelChannelMgnt
* @brief IPC channel management class for Windows AF_UNIX tunnel communication.
*
* Similar to CSocketsChannelMgnt (proto=uds), but:
* - uses CWinTunnelConnection (tunnel wrapper) on top of CWinsockConnection
* - uses proto=tunnel in connect strings
*
* Provides creation and access to tunnel endpoints, manages server-side tunnel lifetimes,
* and integrates with the SDV object/component framework.
*/
class CSocketsTunnelChannelMgnt :
public sdv::CSdvObject,
public sdv::ipc::ICreateEndpoint,
public sdv::ipc::IChannelAccess
{
public:
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::ipc::IChannelAccess)
SDV_INTERFACE_ENTRY(sdv::ipc::ICreateEndpoint)
END_SDV_INTERFACE_MAP()
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("WinTunnelChannelControl")
DECLARE_OBJECT_CLASS_ALIAS("LocalChannelControl")
DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl")
DECLARE_OBJECT_SINGLETON()
virtual ~CSocketsTunnelChannelMgnt() = default;
/**
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
* @return Returns 'true' when the initialization was successful, 'false' when not.
*/
virtual bool OnInitialize() override;
/**
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
*/
virtual void OnShutdown() override;
/**
* @brief Creates a tunnel endpoint (server side) and returns endpoint info.
* @param[in] cfgStr Optional config string (TOML or connect string).
* @return The channel endpoint structure.
*/
sdv::ipc::SChannelEndpoint CreateEndpoint(const sdv::u8string& cfgStr) override;
/**
* @brief Creates or accesses a connection object from the channel connect string.
* @param[in] cs The channel connect string.
* @return Pointer to connection access interface.
*/
sdv::IInterfaceAccess* Access(const sdv::u8string& cs) override;
/**
* @brief Called by server tunnel when closing (bookkeeping).
* @param[in] udsPath The UDS path for the server.
* @param[in] ptr Pointer to the tunnel connection being closed.
*/
void OnServerClosed(const std::string& udsPath, CWinTunnelConnection* ptr);
private:
std::mutex m_udsMtx;
std::map<std::string, std::shared_ptr<CWinTunnelConnection>> m_udsServers;
std::set<std::string> m_udsServerClaimed;
};
DEFINE_SDV_OBJECT(CSocketsTunnelChannelMgnt)
#endif // ! defined WIN_TUNNEL_CHANNEL_MGNT_H
#endif

View File

@@ -0,0 +1,226 @@
/************************************************************
* 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
************************************************************/
#ifdef _WIN32
#include "connection.h"
CWinTunnelConnection::CWinTunnelConnection(
std::shared_ptr<CWinsockConnection> transport,
uint16_t channelId)
: m_Transport(std::move(transport))
, m_ChannelId(channelId)
{
// No additional initialization required; acts as a thin wrapper.
}
bool CWinTunnelConnection::SendData(
/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
{
if (!m_Transport)
{
SDV_LOG_ERROR("[WinTunnel] SendData failed: transport is null");
return false;
}
// Build tunnel header buffer
sdv::pointer<uint8_t> hdrBuf;
hdrBuf.resize(sizeof(STunnelHeader));
STunnelHeader hdr{};
hdr.uiChannelId = m_ChannelId; // Logical channel for this connection
hdr.uiFlags = 0; // Reserved for future use
std::memcpy(hdrBuf.get(), &hdr, sizeof(STunnelHeader));
// Compose new sequence: [header] + original payload chunks
sdv::sequence<sdv::pointer<uint8_t>> seqWithHdr;
seqWithHdr.push_back(hdrBuf);
for (auto& chunk : seqData)
{
seqWithHdr.push_back(chunk);
}
bool result = m_Transport->SendData(seqWithHdr);
if (!result) {
SDV_LOG_ERROR("[WinTunnel] SendData failed in underlying transport");
}
return result;
}
bool CWinTunnelConnection::AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver)
{
if (!m_Transport)
{
SDV_LOG_ERROR("[WinTunnel] AsyncConnect failed: transport is null");
return false;
}
// Store upper-layer callbacks (safe for null)
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
sdv::TInterfaceAccessPtr acc(pReceiver);
m_pUpperReceiver = acc.GetInterface<sdv::ipc::IDataReceiveCallback>();
m_pUpperEvent = acc.GetInterface<sdv::ipc::IConnectEventCallback>();
}
// Register this tunnel as the data/event receiver in the AF_UNIX transport.
bool result = m_Transport->AsyncConnect(this);
if (!result) {
SDV_LOG_ERROR("[WinTunnel] AsyncConnect failed in underlying transport");
}
return result;
}
bool CWinTunnelConnection::WaitForConnection(/*in*/ uint32_t uiWaitMs)
{
if (!m_Transport)
{
SDV_LOG_ERROR("[WinTunnel] WaitForConnection failed: transport is null");
return false;
}
return m_Transport->WaitForConnection(uiWaitMs);
}
void CWinTunnelConnection::CancelWait()
{
if (!m_Transport)
{
SDV_LOG_ERROR("[WinTunnel] CancelWait failed: transport is null");
return;
}
m_Transport->CancelWait();
}
void CWinTunnelConnection::Disconnect()
{
if (!m_Transport)
{
SDV_LOG_ERROR("[WinTunnel] Disconnect failed: transport is null");
return;
}
m_Transport->Disconnect();
// Clear upper-layer callbacks (thread-safe)
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
m_pUpperReceiver = nullptr;
m_pUpperEvent = nullptr;
}
}
uint64_t CWinTunnelConnection::RegisterStateEventCallback(
/*in*/ sdv::IInterfaceAccess* pEventCallback)
{
if (!m_Transport)
{
SDV_LOG_ERROR("[WinTunnel] RegisterStateEventCallback failed: transport is null");
return 0ULL;
}
// Forward directly to underlying CWinsockConnection
return m_Transport->RegisterStateEventCallback(pEventCallback);
}
void CWinTunnelConnection::UnregisterStateEventCallback(
/*in*/ uint64_t uiCookie)
{
if (!m_Transport || uiCookie == 0ULL)
{
SDV_LOG_ERROR("[WinTunnel] UnregisterStateEventCallback failed: transport is null or cookie is 0");
return;
}
m_Transport->UnregisterStateEventCallback(uiCookie);
}
sdv::ipc::EConnectState CWinTunnelConnection::GetConnectState() const
{
if (!m_Transport)
{
return sdv::ipc::EConnectState::uninitialized;
}
return m_Transport->GetConnectState();
}
void CWinTunnelConnection::DestroyObject()
{
Disconnect();
std::lock_guard<std::mutex> lock(m_CallbackMtx);
m_Transport.reset();
}
void CWinTunnelConnection::ReceiveData(
/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
{
// Expect at least one chunk (the tunnel header)
if (seqData.empty())
{
SDV_LOG_ERROR("[WinTunnel] ReceiveData: empty sequence");
return; // nothing to do
}
const auto& hdrChunk = seqData[0];
if (hdrChunk.size() < sizeof(STunnelHeader))
{
SDV_LOG_ERROR("[WinTunnel] ReceiveData: invalid tunnel header size");
// Invalid tunnel frame; drop it for now (could set communication_error)
return;
}
STunnelHeader hdr{};
std::memcpy(&hdr, hdrChunk.get(), sizeof(STunnelHeader));
// TODO: use channelId for multiplexing later
// Build payload-only sequence: drop header chunk, keep others
sdv::sequence<sdv::pointer<uint8_t>> payload;
for (size_t i = 1; i < seqData.size(); ++i)
{
payload.push_back(seqData[i]);
}
if (m_pUpperReceiver)
{
try {
m_pUpperReceiver->ReceiveData(payload); // header stripped
} catch (...) {
SDV_LOG_ERROR("[WinTunnel] Exception in upper receiver's ReceiveData");
}
}
}
void CWinTunnelConnection::SetConnectState(sdv::ipc::EConnectState state)
{
sdv::ipc::IConnectEventCallback* upper = nullptr;
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
upper = m_pUpperEvent;
}
if (upper)
{
try
{
upper->SetConnectState(state);
}
catch (...)
{
SDV_LOG_ERROR("[WinTunnel] Exception in upper event callback's SetConnectState");
// Never let user callback crash the transport.
}
}
}
#endif

View File

@@ -0,0 +1,168 @@
/************************************************************
* 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
************************************************************/
#ifdef _WIN32
#ifndef UDS_WIN_TUNNEL_CONNECTION_H
#define UDS_WIN_TUNNEL_CONNECTION_H
#include <interfaces/ipc.h>
#include <support/component_impl.h>
#include <support/interface_ptr.h>
#include <WinSock2.h>
#include <atomic>
#include <cstdint>
#include <mutex>
#include <memory>
#include <thread>
#include <vector>
#include "../sdv_services/uds_win_sockets/connection.h" // existing AF_UNIX transport: CWinsockConnection
/**
* @brief Logical tunnel connection on top of a shared Windows AF_UNIX transport.
*
* This class does NOT create sockets by itself. It wraps an existing
* CWinsockConnection (Winsock AF_UNIX) and adds:
* - tunnel header (channelId, flags)
* - (later) demultiplexing of incoming payloads per logical channel.
*/
class CWinTunnelConnection :
public sdv::IInterfaceAccess,
public sdv::IObjectDestroy,
public sdv::ipc::IDataSend,
public sdv::ipc::IConnect,
public sdv::ipc::IDataReceiveCallback,
public sdv::ipc::IConnectEventCallback
{
public:
/**
* @struct STunnelHeader
* @brief Header prepended to each tunneled SDV message for logical channel identification and flags.
*/
struct STunnelHeader
{
uint16_t uiChannelId; ///< Logical channel ID (IPC_x / REMOTE_IPC_x)
uint16_t uiFlags; ///< Reserved for future use (QoS, direction, etc.)
};
/**
* @brief Construct a tunnel wrapper over an existing AF_UNIX transport.
* @param[in] transport Shared pointer to the underlying AF_UNIX transport.
* @param[in] channelId Logical channel ID for this tunnel instance.
*/
explicit CWinTunnelConnection(
std::shared_ptr<CWinsockConnection> transport,
uint16_t channelId);
/**
* @brief Destructor.
*/
virtual ~CWinTunnelConnection() = default;
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend)
SDV_INTERFACE_ENTRY(sdv::ipc::IConnect)
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
END_SDV_INTERFACE_MAP()
// ---------- IDataSend ----------
/**
* @brief Send a sequence of buffers via the tunnel.
* @param[in,out] seqData Sequence of message buffers (may be modified by callee).
* @return true on successful send, false otherwise.
*/
bool SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override;
// ---------- IConnect ----------
/**
* @brief Start asynchronous connect and register this object as receiver.
* @param[in] pReceiver Pointer to callback interface for data and state notifications.
* @return true if connect started, false otherwise.
*/
bool AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver) override;
/**
* @brief Wait until the underlying transport becomes 'connected'.
* @param[in] uiWaitMs Timeout in milliseconds to wait.
* @return true if connection established, false on timeout or error.
*/
bool WaitForConnection(/*in*/ uint32_t uiWaitMs) override;
/**
* @brief Cancel any pending connect or wait operation.
*/
void CancelWait() override;
/**
* @brief Disconnect the tunnel and underlying transport.
*/
void Disconnect() override;
/**
* @brief Register a state event callback (forwards to transport).
* @param[in] pEventCallback Pointer to event callback interface.
* @return Registration cookie (nonzero) or 0 on failure.
*/
uint64_t RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
/**
* @brief Unregister a previously registered state event callback.
* @param[in] uiCookie Registration cookie returned by RegisterStateEventCallback.
*/
void UnregisterStateEventCallback(/*in*/ uint64_t uiCookie) override;
/**
* @brief Get the current state from the underlying transport.
* @return The current connection state.
*/
sdv::ipc::EConnectState GetConnectState() const override;
// ---------- IObjectDestroy ----------
/**
* @brief Release and clean up all resources associated with this object.
*/
void DestroyObject() override;
// ---------- IDataReceiveCallback ----------
/**
* @brief Receive data from the underlying AF_UNIX transport.
* @param[in,out] seqData Sequence of received message buffers (header chunk is removed by this call).
*/
void ReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override;
// ---------- IConnectEventCallback ----------
/**
* @brief Forward state changes from the underlying transport to the upper layer.
* @param[in] state New connection state.
*/
void SetConnectState(sdv::ipc::EConnectState state) override;
// Helpers
void SetChannelId(uint16_t channelId) { m_ChannelId = channelId; }
uint16_t GetChannelId() const noexcept { return m_ChannelId; }
private:
std::shared_ptr<CWinsockConnection> m_Transport; ///< shared physical tunnel port
uint16_t m_ChannelId{0}; ///< default logical channel id
// Upper layer callbacks (original VAPI receiver)
sdv::ipc::IDataReceiveCallback* m_pUpperReceiver{nullptr};
sdv::ipc::IConnectEventCallback* m_pUpperEvent{nullptr};
mutable std::mutex m_CallbackMtx;
};
#endif // UDS_WIN_TUNNEL_CONNECTION_H
#endif

View File

@@ -85,12 +85,10 @@ add_subdirectory(unit_tests/sdv_control)
add_subdirectory(unit_tests/sdv_macro_test)
add_subdirectory(unit_tests/install_package_composer)
add_subdirectory(unit_tests/path_match)
if(UNIX)
add_subdirectory(unit_tests/unix_sockets)
endif()
if(WIN32)
add_subdirectory(unit_tests/unix_tunnel)
add_subdirectory(unit_tests/win_sockets)
endif()
add_subdirectory(unit_tests/win_tunnel)
add_subdirectory(component_tests/config)
add_subdirectory(component_tests/logger)
add_subdirectory(component_tests/data_dispatch_service)

View File

@@ -1085,9 +1085,9 @@ ViewFilter = "Fatal"
// Disconnect from the client
sdv::ipc::IConnect* pClientConnect = ptrClientEndpoint.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(pClientConnect, nullptr);
EXPECT_EQ(pClientConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
control.RemoveConnection(tConnectionID);
EXPECT_EQ(pClientConnect->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::disconnected);
ptrClientEndpoint.Clear(); // Lifetime taken over by communication control.
// Create another client endpoint
@@ -1155,9 +1155,9 @@ ViewFilter = "Fatal"
// Disconnect from the client
sdv::ipc::IConnect* pClientConnect = ptrClientEndpoint.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(pClientConnect, nullptr);
EXPECT_EQ(pClientConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
control.RemoveConnection(tConnectionID);
EXPECT_EQ(pClientConnect->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(pClientConnect->GetConnectState(), sdv::ipc::EConnectState::disconnected);
ptrClientEndpoint.Clear(); // Lifetime taken over by communication control.
// Create another client endpoint

View File

@@ -117,14 +117,14 @@ public:
}
/**
* @brief Set the current status. Overload of sdv::ipc::IConnectEventCallback::SetStatus.
* @param[in] eConnectStatus The connection status.
* @brief Set the current state. Overload of sdv::ipc::IConnectEventCallback::SetConnectState.
* @param[in] eConnectState The connection state.
*/
virtual void SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus) override
virtual void SetConnectState(/*in*/ sdv::ipc::EConnectState eConnectState) override
{
switch (eConnectStatus)
switch (eConnectState)
{
case sdv::ipc::EConnectStatus::disconnected:
case sdv::ipc::EConnectState::disconnected:
// Disconnect only when connected before
if (m_bConnected)
{
@@ -133,7 +133,7 @@ public:
m_cvDisconnect.notify_all();
}
break;
case sdv::ipc::EConnectStatus::disconnected_forced:
case sdv::ipc::EConnectState::disconnected_forced:
// Disconnect only when connected before
if (m_bConnected)
{
@@ -142,7 +142,7 @@ public:
m_cvDisconnect.notify_all();
}
break;
case sdv::ipc::EConnectStatus::connected:
case sdv::ipc::EConnectState::connected:
m_bConnected = true;
break;
default:
@@ -339,11 +339,11 @@ extern "C" int main(int argc, char* argv[])
if (!ptrControlConnection) return -12;
pControlConnect = ptrControlConnection.GetInterface<sdv::ipc::IConnect>();
if (!pControlConnect) return -13;
uiControlEventCookie = pControlConnect->RegisterStatusEventCallback(&receiverControl);
uiControlEventCookie = pControlConnect->RegisterStateEventCallback(&receiverControl);
if (!uiControlEventCookie) return -20;
if (!pControlConnect->AsyncConnect(&receiverControl)) return -14;
if (!pControlConnect->WaitForConnection(250)) return -5; // Note: Connection should be possible within 250ms.
if (pControlConnect->GetStatus() != sdv::ipc::EConnectStatus::connected) return -15;
if (pControlConnect->GetConnectState() != sdv::ipc::EConnectState::connected) return -15;
TRACE(bServer ? "Server" : "Client", ": Connected to control channel...");
}
@@ -393,11 +393,11 @@ Size = 1024000
sdv::ipc::IConnect* pDataConnect = ptrDataConnection.GetInterface<sdv::ipc::IConnect>();
uint64_t uiDataEventCookie = 0;
if (!pDataConnect) return -3;
uiDataEventCookie = pDataConnect->RegisterStatusEventCallback(&receiverData);
uiDataEventCookie = pDataConnect->RegisterStateEventCallback(&receiverData);
if (!uiDataEventCookie) return -21;
if (!pDataConnect->AsyncConnect(&receiverData)) return -4;
if (!pDataConnect->WaitForConnection(10000)) return -5; // Note: Connection should be possible within 10000ms.
if (pDataConnect->GetStatus() != sdv::ipc::EConnectStatus::connected) return -5;
if (pDataConnect->GetConnectState() != sdv::ipc::EConnectState::connected) return -5;
TRACE("Connected to data channel... waiting for data exchange");
@@ -430,11 +430,11 @@ Size = 1024000
}
// Initiate shutdown
if (pDataConnect && uiDataEventCookie) pDataConnect->UnregisterStatusEventCallback(uiDataEventCookie);
if (pDataConnect && uiDataEventCookie) pDataConnect->UnregisterStateEventCallback(uiDataEventCookie);
ptrDataConnection.Clear();
mgntDataMgntChannel.Shutdown();
if (mgntDataMgntChannel.GetObjectState() != sdv::EObjectState::destruction_pending) return -6;
if (pControlConnect && uiControlEventCookie) pControlConnect->UnregisterStatusEventCallback(uiControlEventCookie);
if (pControlConnect && uiControlEventCookie) pControlConnect->UnregisterStateEventCallback(uiControlEventCookie);
ptrControlConnection.Clear();
mgntControlMgntChannel.Shutdown();
if (mgntControlMgntChannel.GetObjectState() != sdv::EObjectState::destruction_pending) return -16;

View File

@@ -89,33 +89,33 @@ public:
}
/**
* @brief Set the current status. Overload of sdv::ipc::IConnectEventCallback::SetStatus.
* @param[in] eConnectStatus The connection status.
* @brief Set the current state. Overload of sdv::ipc::IConnectEventCallback::SetConnectState.
* @param[in] eConnectState The connection state.
*/
virtual void SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus) override
virtual void SetConnectState(/*in*/ sdv::ipc::EConnectState eConnectState) override
{
switch (eConnectStatus)
switch (eConnectState)
{
case sdv::ipc::EConnectStatus::connection_error:
case sdv::ipc::EConnectState::connection_error:
m_bConnectError = true;
break;
case sdv::ipc::EConnectStatus::communication_error:
case sdv::ipc::EConnectState::communication_error:
m_bCommError = true;
break;
case sdv::ipc::EConnectStatus::disconnected_forced:
case sdv::ipc::EConnectState::disconnected_forced:
m_bForcedDisconnect = true;
break;
default:
break;
}
m_eStatus = eConnectStatus;
m_eConnectState = eConnectState;
}
/**
* @brief Return the received connection status.
* @return The received connection status.
* @brief Return the received connection state.
* @return The received connection state.
*/
sdv::ipc::EConnectStatus GetReceivedStatus() const { return m_eStatus; }
sdv::ipc::EConnectState GetReceivedState() const { return m_eConnectState; }
/**
* @brief Has a connection error occurred?
@@ -136,9 +136,9 @@ public:
bool ForcedDisconnectOccurred() const { return m_bForcedDisconnect; }
/**
* @brief Reset the received status event flags.
* @brief Reset the received state event flags.
*/
void ResetStatusEvents()
void ResetStateEvents()
{
m_bConnectError = false;
m_bCommError = false;
@@ -182,7 +182,7 @@ private:
sdv::ipc::IDataSend* m_pSend = nullptr; ///< Send interface to implement repeating function.
mutable std::mutex m_mtxData; ///< Protect data access.
sdv::sequence<sdv::pointer<uint8_t>> m_seqDataCopy; ///< Copy of the data.
sdv::ipc::EConnectStatus m_eStatus = sdv::ipc::EConnectStatus::uninitialized; ///< Current received status.
sdv::ipc::EConnectState m_eConnectState = sdv::ipc::EConnectState::uninitialized; ///< Current received state.
bool m_bConnectError = false; ///< Connection error ocurred.
bool m_bCommError = false; ///< Communication error occurred.
bool m_bForcedDisconnect = false; ///< Force disconnect.
@@ -355,8 +355,8 @@ TEST(SharedMemChannelService, WaitForConnection)
// Wait for connection with timeout
EXPECT_FALSE(pConnection->WaitForConnection(100));
EXPECT_TRUE(pConnection->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
pConnection->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pConnection->GetConnectState() == sdv::ipc::EConnectState::initialized ||
pConnection->GetConnectState() == sdv::ipc::EConnectState::connecting);
// Wait for connection for infinite period with cancel.
std::thread threadCancelWait([&]()
@@ -366,8 +366,8 @@ TEST(SharedMemChannelService, WaitForConnection)
});
EXPECT_FALSE(pConnection->WaitForConnection(0xffffffff)); // Note: wait indefinitely. Cancel get called in 500ms.
threadCancelWait.join();
EXPECT_TRUE(pConnection->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
pConnection->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pConnection->GetConnectState() == sdv::ipc::EConnectState::initialized ||
pConnection->GetConnectState() == sdv::ipc::EConnectState::connecting);
EXPECT_NO_THROW(ptrConnection.Clear());
@@ -427,8 +427,8 @@ TEST(SharedMemChannelService, AsyncConnect)
ASSERT_NE(pServerConnect, nullptr);
EXPECT_TRUE(pServerConnect->AsyncConnect(&receiverServer));
EXPECT_FALSE(pServerConnect->WaitForConnection(25)); // Note: 25ms will not get a connection.
EXPECT_TRUE(pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnect->GetConnectState() == sdv::ipc::EConnectState::initialized ||
pServerConnect->GetConnectState() == sdv::ipc::EConnectState::connecting);
// Try send; should fail, since not connected
EXPECT_FALSE(pServerSend->SendData(seqPattern));
@@ -439,8 +439,8 @@ TEST(SharedMemChannelService, AsyncConnect)
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient));
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_TRUE(pServerConnect->WaitForConnection(1000)); // Note: 1000ms to also receive the connection at the server.
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
// Try send; should succeed, since connected
EXPECT_TRUE(pServerSend->SendData(seqPattern));
@@ -520,39 +520,39 @@ TEST(SharedMemChannelService, EstablishConnectionEvents)
EXPECT_FALSE(receiverServer.CommunicationbErrorOccurred());
EXPECT_FALSE(pServerSend->SendData(seqPattern));
EXPECT_FALSE(receiverServer.CommunicationbErrorOccurred()); // No events registered yet... only after a call to AsyncConnect.
receiverServer.ResetStatusEvents();
receiverServer.ResetStateEvents();
// Establish the server connection
sdv::ipc::IConnect* pServerConnect = ptrServerConnection.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(pServerConnect, nullptr);
EXPECT_NE(pServerConnect->RegisterStatusEventCallback(&receiverServer), 0);
EXPECT_NE(pServerConnect->RegisterStateEventCallback(&receiverServer), 0);
EXPECT_TRUE(pServerConnect->AsyncConnect(&receiverServer));
EXPECT_FALSE(pServerConnect->WaitForConnection(25)); // Note: 25ms will not get a connection.
EXPECT_TRUE(pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(receiverServer.GetReceivedStatus() == sdv::ipc::EConnectStatus::initialized ||
receiverServer.GetReceivedStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnect->GetConnectState() == sdv::ipc::EConnectState::initialized ||
pServerConnect->GetConnectState() == sdv::ipc::EConnectState::connecting);
EXPECT_TRUE(receiverServer.GetReceivedState() == sdv::ipc::EConnectState::initialized ||
receiverServer.GetReceivedState() == sdv::ipc::EConnectState::connecting);
EXPECT_FALSE(receiverServer.ConnectionErrorOccurred());
// Try send; should fail, since not connected
EXPECT_FALSE(receiverServer.CommunicationbErrorOccurred());
EXPECT_FALSE(pServerSend->SendData(seqPattern));
EXPECT_TRUE(receiverServer.CommunicationbErrorOccurred());
receiverServer.ResetStatusEvents();
receiverServer.ResetStateEvents();
// Establish the client connection
sdv::ipc::IConnect* pClientConnection = ptrClientConnection.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(pClientConnection, nullptr);
EXPECT_NE(pClientConnection->RegisterStatusEventCallback(&receiverClient), 0);
EXPECT_NE(pClientConnection->RegisterStateEventCallback(&receiverClient), 0);
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient));
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_TRUE(pServerConnect->WaitForConnection(1000)); // Note: 1000ms to also receive the connection at the server.
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_FALSE(receiverServer.ConnectionErrorOccurred());
EXPECT_FALSE(receiverClient.ConnectionErrorOccurred());
EXPECT_EQ(receiverServer.GetReceivedStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(receiverClient.GetReceivedStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(receiverServer.GetReceivedState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(receiverClient.GetReceivedState(), sdv::ipc::EConnectState::connected);
// Try send; should succeed, since connected
EXPECT_TRUE(pServerSend->SendData(seqPattern));
@@ -621,8 +621,8 @@ TEST(SharedMemChannelService, EstablishReconnect)
ASSERT_NE(pServerConnect, nullptr);
EXPECT_TRUE(pServerConnect->AsyncConnect(&receiverServer));
EXPECT_FALSE(pServerConnect->WaitForConnection(25)); // Note: 25ms will not get a connection.
EXPECT_TRUE(pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnect->GetConnectState() == sdv::ipc::EConnectState::initialized ||
pServerConnect->GetConnectState() == sdv::ipc::EConnectState::connecting);
std::cout << "Connect client 1..." << std::endl;
// Establish the client1 connection
@@ -631,8 +631,8 @@ TEST(SharedMemChannelService, EstablishReconnect)
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient1));
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_TRUE(pServerConnect->WaitForConnection(1000)); // Note: 1000ms to also receive the connection at the server.
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
std::cout << "Disconnect client 1..." << std::endl;
@@ -640,10 +640,10 @@ TEST(SharedMemChannelService, EstablishReconnect)
EXPECT_NO_THROW(ptrClient1Connection.Clear());
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Note: Handle connection terminate.
EXPECT_NO_THROW(mgntClient1.Shutdown());
EXPECT_TRUE(pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::disconnected ||
pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnect->GetConnectState() == sdv::ipc::EConnectState::disconnected ||
pServerConnect->GetConnectState() == sdv::ipc::EConnectState::connecting);
std::cout << "Connect client 2... (eStatus = " << (uint32_t) pServerConnect->GetStatus() << ")" << std::endl;
std::cout << "Connect client 2... (eConnectState = " << (uint32_t) pServerConnect->GetConnectState() << ")" << std::endl;
// Establish the client2 connection
sdv::TObjectPtr ptrClient2Connection = mgntClient2.Access(sChannelEndpoint.ssConnectString);
@@ -653,8 +653,8 @@ TEST(SharedMemChannelService, EstablishReconnect)
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient2));
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_TRUE(pServerConnect->WaitForConnection(1000)); // Note: 1000ms to also receive the connection at the server.
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
std::cout << "Disconnect client 2..." << std::endl;
@@ -662,8 +662,8 @@ TEST(SharedMemChannelService, EstablishReconnect)
EXPECT_NO_THROW(ptrClient2Connection.Clear());
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Note: Handle connection terminate
EXPECT_NO_THROW(mgntClient2.Shutdown());
EXPECT_TRUE(pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::disconnected ||
pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnect->GetConnectState() == sdv::ipc::EConnectState::disconnected ||
pServerConnect->GetConnectState() == sdv::ipc::EConnectState::connecting);
std::cout << "Shutdown server..." << std::endl;
@@ -702,13 +702,13 @@ TEST(SharedMemChannelService, EstablishReconnectEvents)
// Establish the server connection
sdv::ipc::IConnect* pServerConnect = ptrServerConnection.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(pServerConnect, nullptr);
EXPECT_NE(pServerConnect->RegisterStatusEventCallback(&receiverServer), 0);
EXPECT_NE(pServerConnect->RegisterStateEventCallback(&receiverServer), 0);
EXPECT_TRUE(pServerConnect->AsyncConnect(&receiverServer));
EXPECT_FALSE(pServerConnect->WaitForConnection(25)); // Note: 25ms will not get a connection.
EXPECT_TRUE(pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(receiverServer.GetReceivedStatus() == sdv::ipc::EConnectStatus::initialized ||
receiverServer.GetReceivedStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnect->GetConnectState() == sdv::ipc::EConnectState::initialized ||
pServerConnect->GetConnectState() == sdv::ipc::EConnectState::connecting);
EXPECT_TRUE(receiverServer.GetReceivedState() == sdv::ipc::EConnectState::initialized ||
receiverServer.GetReceivedState() == sdv::ipc::EConnectState::connecting);
EXPECT_FALSE(receiverServer.ConnectionErrorOccurred());
std::cout << "Connect client 1..." << std::endl;
@@ -716,16 +716,16 @@ TEST(SharedMemChannelService, EstablishReconnectEvents)
// Establish the client1 connection
sdv::ipc::IConnect* pClientConnection = ptrClient1Connection.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(pClientConnection, nullptr);
EXPECT_NE(pClientConnection->RegisterStatusEventCallback(&receiverClient1), 0);
EXPECT_NE(pClientConnection->RegisterStateEventCallback(&receiverClient1), 0);
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient1));
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_TRUE(pServerConnect->WaitForConnection(1000)); // Note: 1000ms to also receive the connection at the server.
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_FALSE(receiverServer.ConnectionErrorOccurred());
EXPECT_FALSE(receiverClient1.ConnectionErrorOccurred());
EXPECT_EQ(receiverServer.GetReceivedStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(receiverClient1.GetReceivedStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(receiverServer.GetReceivedState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(receiverClient1.GetReceivedState(), sdv::ipc::EConnectState::connected);
std::cout << "Disconnect client 1..." << std::endl;
@@ -733,10 +733,10 @@ TEST(SharedMemChannelService, EstablishReconnectEvents)
EXPECT_NO_THROW(ptrClient1Connection.Clear());
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Note: Handle connection terminate.
EXPECT_NO_THROW(mgntClient1.Shutdown());
EXPECT_TRUE(pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::disconnected ||
pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(receiverServer.GetReceivedStatus() == sdv::ipc::EConnectStatus::disconnected ||
receiverServer.GetReceivedStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnect->GetConnectState() == sdv::ipc::EConnectState::disconnected ||
pServerConnect->GetConnectState() == sdv::ipc::EConnectState::connecting);
EXPECT_TRUE(receiverServer.GetReceivedState() == sdv::ipc::EConnectState::disconnected ||
receiverServer.GetReceivedState() == sdv::ipc::EConnectState::connecting);
EXPECT_FALSE(receiverServer.ConnectionErrorOccurred());
std::cout << "Connect client 2..." << std::endl;
@@ -746,16 +746,16 @@ TEST(SharedMemChannelService, EstablishReconnectEvents)
EXPECT_TRUE(ptrClient2Connection);
pClientConnection = ptrClient2Connection.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(pClientConnection, nullptr);
EXPECT_NE(pClientConnection->RegisterStatusEventCallback(&receiverClient2), 0);
EXPECT_NE(pClientConnection->RegisterStateEventCallback(&receiverClient2), 0);
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient2));
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_TRUE(pServerConnect->WaitForConnection(1000)); // Note: 1000ms to also receive the connection at the server.
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_FALSE(receiverServer.ConnectionErrorOccurred());
EXPECT_FALSE(receiverClient2.ConnectionErrorOccurred());
EXPECT_EQ(receiverServer.GetReceivedStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(receiverClient2.GetReceivedStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(receiverServer.GetReceivedState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(receiverClient2.GetReceivedState(), sdv::ipc::EConnectState::connected);
std::cout << "Disconnect client 2..." << std::endl;
@@ -763,10 +763,10 @@ TEST(SharedMemChannelService, EstablishReconnectEvents)
EXPECT_NO_THROW(ptrClient2Connection.Clear());
std::this_thread::sleep_for(std::chrono::milliseconds(25)); // Note: Handle connection terminate
EXPECT_NO_THROW(mgntClient2.Shutdown());
EXPECT_TRUE(pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::disconnected ||
pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(receiverServer.GetReceivedStatus() == sdv::ipc::EConnectStatus::disconnected ||
receiverServer.GetReceivedStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnect->GetConnectState() == sdv::ipc::EConnectState::disconnected ||
pServerConnect->GetConnectState() == sdv::ipc::EConnectState::connecting);
EXPECT_TRUE(receiverServer.GetReceivedState() == sdv::ipc::EConnectState::disconnected ||
receiverServer.GetReceivedState() == sdv::ipc::EConnectState::connecting);
EXPECT_FALSE(receiverServer.ConnectionErrorOccurred());
std::cout << "Shutdown server..." << std::endl;
@@ -828,7 +828,7 @@ Mode = "Essential")code"));
ASSERT_NE(pServerConnect, nullptr);
EXPECT_TRUE(pServerConnect->AsyncConnect(&receiverServer));
EXPECT_TRUE(pServerConnect->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
// Try send; should succeed, since connected
EXPECT_TRUE(pServerSend->SendData(seqPattern));
@@ -909,11 +909,11 @@ Mode = "Essential")code"));
ASSERT_NE(pServerConnect, nullptr);
EXPECT_TRUE(pServerConnect->AsyncConnect(&receiverServer));
EXPECT_TRUE(pServerConnect->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
// Try send; should succeed, since connected
auto tpStart = std::chrono::high_resolution_clock::now();
while (pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::connected)
while (pServerConnect->GetConnectState() == sdv::ipc::EConnectState::connected)
{
EXPECT_TRUE(pServerSend->SendData(seqPattern));
std::this_thread::sleep_for(std::chrono::milliseconds(300));
@@ -925,7 +925,7 @@ Mode = "Essential")code"));
break; // Max 4000 ms
}
}
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::disconnected);
EXPECT_NO_THROW(ptrServerConnection.Clear());
@@ -984,14 +984,14 @@ Mode = "Essential")code"));
// Establish the server connection
sdv::ipc::IConnect* pServerConnect = ptrServerConnection.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(pServerConnect, nullptr);
EXPECT_NE(pServerConnect->RegisterStatusEventCallback(&receiverServer), 0);
EXPECT_NE(pServerConnect->RegisterStateEventCallback(&receiverServer), 0);
EXPECT_TRUE(pServerConnect->AsyncConnect(&receiverServer));
EXPECT_TRUE(pServerConnect->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::connected);
// Try send; should succeed, since connected
auto tpStart = std::chrono::high_resolution_clock::now();
while (pServerConnect->GetStatus() == sdv::ipc::EConnectStatus::connected)
while (pServerConnect->GetConnectState() == sdv::ipc::EConnectState::connected)
{
EXPECT_TRUE(pServerSend->SendData(seqPattern));
std::this_thread::sleep_for(std::chrono::milliseconds(300));
@@ -1004,7 +1004,7 @@ Mode = "Essential")code"));
}
}
EXPECT_TRUE(receiverServer.ForcedDisconnectOccurred());
EXPECT_EQ(pServerConnect->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(pServerConnect->GetConnectState(), sdv::ipc::EConnectState::disconnected);
EXPECT_NO_THROW(ptrServerConnection.Clear());
@@ -1048,7 +1048,7 @@ Mode = "Essential")code"));
ASSERT_NE(pControlConnect1, nullptr);
EXPECT_TRUE(pControlConnect1->AsyncConnect(&receiverControl1));
EXPECT_TRUE(pControlConnect1->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_EQ(pControlConnect1->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pControlConnect1->GetConnectState(), sdv::ipc::EConnectState::connected);
// Wait for data
sdv::sequence<sdv::pointer<uint8_t>> seqDataConnectString;
@@ -1084,7 +1084,7 @@ Mode = "Essential")code"));
ASSERT_NE(pControlConnect2, nullptr);
EXPECT_TRUE(pControlConnect2->AsyncConnect(&receiverControl2));
EXPECT_TRUE(pControlConnect2->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_EQ(pControlConnect2->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pControlConnect2->GetConnectState(), sdv::ipc::EConnectState::connected);
// Wait for process termination
pProcessLifetime->WaitForTerminate(tProcessID1, 0xffffffff);
@@ -1131,7 +1131,7 @@ Mode = "Essential")code"));
ASSERT_NE(pControlConnect1, nullptr);
EXPECT_TRUE(pControlConnect1->AsyncConnect(&receiverControl1));
EXPECT_TRUE(pControlConnect1->WaitForConnection(2000)); // Note: Connection should be possible within 5000ms.
EXPECT_EQ(pControlConnect1->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pControlConnect1->GetConnectState(), sdv::ipc::EConnectState::connected);
// Wait for connection string (max. 500*10ms).
sdv::sequence<sdv::pointer<uint8_t>> seqDataConnectString;
@@ -1167,7 +1167,7 @@ Mode = "Essential")code"));
ASSERT_NE(pControlConnect2, nullptr);
EXPECT_TRUE(pControlConnect2->AsyncConnect(&receiverControl2));
EXPECT_TRUE(pControlConnect2->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_EQ(pControlConnect2->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pControlConnect2->GetConnectState(), sdv::ipc::EConnectState::connected);
// Wait for process termination
pProcessLifetime->WaitForTerminate(tProcessID1, 0xffffffff);
@@ -1212,7 +1212,7 @@ Mode = "Essential")code"));
ASSERT_NE(pControlConnect1, nullptr);
EXPECT_TRUE(pControlConnect1->AsyncConnect(&receiverControl1));
EXPECT_TRUE(pControlConnect1->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_EQ(pControlConnect1->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pControlConnect1->GetConnectState(), sdv::ipc::EConnectState::connected);
// Wait for connection string (max. 500*10ms).
sdv::sequence<sdv::pointer<uint8_t>> seqDataConnectString;
@@ -1248,7 +1248,7 @@ Mode = "Essential")code"));
ASSERT_NE(pControlConnect2, nullptr);
EXPECT_TRUE(pControlConnect2->AsyncConnect(&receiverControl2));
EXPECT_TRUE(pControlConnect2->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_EQ(pControlConnect2->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pControlConnect2->GetConnectState(), sdv::ipc::EConnectState::connected);
// Wait for process termination
pProcessLifetime->WaitForTerminate(tProcessID1, 0xffffffff);

View File

@@ -115,7 +115,7 @@ public:
// Wait until there is no activity any more.
std::chrono::high_resolution_clock::time_point tpTickSent = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point tpTickReceive = std::chrono::high_resolution_clock::now();
while (m_eStatus != sdv::ipc::EConnectStatus::disconnected)
while (m_eConnectState != sdv::ipc::EConnectState::disconnected)
{
std::unique_lock<std::mutex> lock(m_mtxData);
m_cvReceived.wait_for(lock, std::chrono::milliseconds(50));
@@ -141,33 +141,33 @@ public:
}
/**
* @brief Set the current status. Overload of sdv::ipc::IConnectEventCallback::SetStatus.
* @param[in] eConnectStatus The connection status.
* @brief Set the current state. Overload of sdv::ipc::IConnectEventCallback::SetConnectState.
* @param[in] eConnectState The connection state.
*/
virtual void SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus) override
virtual void SetConnectState(/*in*/ sdv::ipc::EConnectState eConnectState) override
{
switch (eConnectStatus)
switch (eConnectState)
{
case sdv::ipc::EConnectStatus::connection_error:
case sdv::ipc::EConnectState::connection_error:
m_bConnectError = true;
break;
case sdv::ipc::EConnectStatus::communication_error:
case sdv::ipc::EConnectState::communication_error:
m_bCommError = true;
break;
case sdv::ipc::EConnectStatus::disconnected_forced:
case sdv::ipc::EConnectState::disconnected_forced:
m_bForcedDisconnect = true;
break;
default:
break;
}
m_eStatus = eConnectStatus;
m_eConnectState = eConnectState;
}
/**
* @brief Return the received connection status.
* @return The received connection status.
* @brief Return the received connection state.
* @return The received connection state.
*/
sdv::ipc::EConnectStatus GetReceivedStatus() const { return m_eStatus; }
sdv::ipc::EConnectState GetReceivedState() const { return m_eConnectState; }
/**
* @brief Has a connection error occurred?
@@ -207,9 +207,9 @@ public:
}
/**
* @brief Reset the received status event flags.
* @brief Reset the received state event flags.
*/
void ResetStatusEvents()
void ResetStateEvents()
{
m_bConnectError = false;
m_bCommError = false;
@@ -256,7 +256,7 @@ private:
sdv::ipc::IDataSend* m_pSend = nullptr; ///< Send interface to implement repeating function.
mutable std::mutex m_mtxData; ///< Protect data access.
std::queue<sdv::pointer<uint8_t>> m_queueDataCopy; ///< Copy of the data.
std::atomic<sdv::ipc::EConnectStatus> m_eStatus = sdv::ipc::EConnectStatus::uninitialized; ///< Current received status.
std::atomic<sdv::ipc::EConnectState> m_eConnectState = sdv::ipc::EConnectState::uninitialized; ///< Current received state.
bool m_bConnectError = false; ///< Connection error ocurred.
bool m_bCommError = false; ///< Communication error occurred.
bool m_bForcedDisconnect = false; ///< Force disconnect.
@@ -322,8 +322,8 @@ Size = 1024000
ASSERT_NE(pServerConnection, nullptr);
EXPECT_TRUE(pServerConnection->AsyncConnect(&receiverServer));
EXPECT_FALSE(pServerConnection->WaitForConnection(50)); // Note: 50ms will not get a connection.
EXPECT_TRUE(pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnection->GetConnectState() == sdv::ipc::EConnectState::initialized ||
pServerConnection->GetConnectState() == sdv::ipc::EConnectState::connecting);
// Establish the client connection
sdv::ipc::IConnect* pClientConnection = ptrClientConnection.GetInterface<sdv::ipc::IConnect>();
@@ -331,8 +331,8 @@ Size = 1024000
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient));
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_TRUE(pServerConnection->WaitForConnection(50)); // Note: 50ms to also receive the connection at the server.
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(pServerConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
appcontrol.SetRunningMode();
// Try send; should succeed, since connected
@@ -419,8 +419,8 @@ Size = 1024000
ASSERT_NE(pServerConnection, nullptr);
EXPECT_TRUE(pServerConnection->AsyncConnect(&receiverServer));
EXPECT_FALSE(pServerConnection->WaitForConnection(50)); // Note: 50ms will not get a connection.
EXPECT_TRUE(pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnection->GetConnectState() == sdv::ipc::EConnectState::initialized ||
pServerConnection->GetConnectState() == sdv::ipc::EConnectState::connecting);
// Establish the client connection
sdv::ipc::IConnect* pClientConnection = ptrClientConnection.GetInterface<sdv::ipc::IConnect>();
@@ -428,8 +428,8 @@ Size = 1024000
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient));
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_TRUE(pServerConnection->WaitForConnection(50)); // Note: 50ms to also receive the connection at the server.
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(pServerConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
appcontrol.SetRunningMode();
// Try send; should succeed, since connected
@@ -551,8 +551,8 @@ Size = 1024000
ASSERT_NE(pServerConnection, nullptr);
EXPECT_TRUE(pServerConnection->AsyncConnect(&receiverServer));
EXPECT_FALSE(pServerConnection->WaitForConnection(50)); // Note: 50ms will not get a connection.
EXPECT_TRUE(pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::initialized ||
pServerConnection->GetStatus() == sdv::ipc::EConnectStatus::connecting);
EXPECT_TRUE(pServerConnection->GetConnectState() == sdv::ipc::EConnectState::initialized ||
pServerConnection->GetConnectState() == sdv::ipc::EConnectState::connecting);
// Establish the client connection
sdv::ipc::IConnect* pClientConnection = ptrClientConnection.GetInterface<sdv::ipc::IConnect>();
@@ -560,8 +560,8 @@ Size = 1024000
EXPECT_TRUE(pClientConnection->AsyncConnect(&receiverClient));
EXPECT_TRUE(pClientConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_TRUE(pServerConnection->WaitForConnection(50)); // Note: 50ms to also receive the connection at the server.
EXPECT_EQ(pClientConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pClientConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(pServerConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
appcontrol.SetRunningMode();
// Try send; should succeed, since connected
@@ -666,7 +666,7 @@ Size = 1024000
ASSERT_NE(pServerConnection, nullptr);
EXPECT_TRUE(pServerConnection->AsyncConnect(&receiverServer));
EXPECT_TRUE(pServerConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_EQ(pServerConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
TRACE("Connection estabished...");
appcontrol.SetRunningMode();
@@ -750,7 +750,7 @@ Size = 1024000
ASSERT_NE(pServerConnection, nullptr);
EXPECT_TRUE(pServerConnection->AsyncConnect(&receiverServer));
EXPECT_TRUE(pServerConnection->WaitForConnection(2000)); // Note: Connection should be possible within 2000ms.
EXPECT_EQ(pServerConnection->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(pServerConnection->GetConnectState(), sdv::ipc::EConnectState::connected);
TRACE("Connection estabished...");
appcontrol.SetRunningMode();

View File

@@ -6,8 +6,11 @@
# 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(UnixSocketCommunicationTests LANGUAGES CXX)
@@ -26,11 +29,19 @@ target_include_directories(UnitTest_UnixSocketConnectTests PUBLIC
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_link_libraries(UnitTest_UnixSocketConnectTests
GTest::GTest ${CMAKE_THREAD_LIBS_INIT} stdc++fs ${CMAKE_DL_LIBS} rt
GTest::GTest
${CMAKE_THREAD_LIBS_INIT}
stdc++fs ${CMAKE_DL_LIBS}
rt
uds_unix_sockets
)
else()
target_link_libraries(UnitTest_UnixSocketConnectTests
GTest::GTest Ws2_32 Winmm Rpcrt4.lib
GTest::GTest
Ws2_32
Winmm
Rpcrt4.lib
uds_unix_sockets
)
endif()
@@ -46,4 +57,6 @@ add_custom_command(TARGET UnitTest_UnixSocketConnectTests POST_BUILD
--gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_UnixSocketConnectTests.xml
VERBATIM
)
endif()
endif()

View File

@@ -1,12 +1,15 @@
/********************************************************************************
* 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
********************************************************************************/
* 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__
@@ -15,8 +18,8 @@
#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 "../../../sdv_services/uds_unix_sockets/channel_mgnt.h"
#include "../../../sdv_services/uds_unix_sockets/connection.h"
#include <thread>
#include <atomic>
@@ -24,6 +27,7 @@
#include <mutex>
#include <condition_variable>
#include <cstring>
#include <sys/un.h>
#include <sstream>
#include <iomanip>
@@ -44,24 +48,24 @@ public:
// don't test data path yet
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>& /*seqData*/) override {}
void SetStatus(sdv::ipc::EConnectStatus s) override {
void SetConnectState(sdv::ipc::EConnectState s) override {
{
std::lock_guard<std::mutex> lk(m_mtx);
m_status = s;
m_state = s;
}
m_cv.notify_all();
}
bool WaitForStatus(sdv::ipc::EConnectStatus expected, uint32_t ms = 2000)
bool WaitForState(sdv::ipc::EConnectState expected, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
if (m_status == expected)
if (m_state == expected)
return true;
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms);
while (m_status != expected)
while (m_state != expected)
{
if (m_cv.wait_until(lk, deadline) == std::cv_status::timeout)
return false;
@@ -69,13 +73,13 @@ public:
return true;
}
sdv::ipc::EConnectStatus GetStatus() const {
sdv::ipc::EConnectState GetConnectState() const {
std::lock_guard<std::mutex> lk(m_mtx);
return m_status;
return m_state;
}
private:
sdv::ipc::EConnectStatus m_status { sdv::ipc::EConnectStatus::uninitialized };
sdv::ipc::EConnectState m_state { sdv::ipc::EConnectState::uninitialized };
mutable std::mutex m_mtx;
std::condition_variable m_cv;
};
@@ -101,24 +105,24 @@ public:
m_cv.notify_all();
}
void SetStatus(sdv::ipc::EConnectStatus s) override {
void SetConnectState(sdv::ipc::EConnectState s) override {
{
std::lock_guard<std::mutex> lk(m_mtx);
m_status = s;
m_state = s;
}
m_cv.notify_all();
}
bool WaitForStatus(sdv::ipc::EConnectStatus expected, uint32_t ms = 2000)
bool WaitForState(sdv::ipc::EConnectState expected, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
if (m_status == expected)
if (m_state == expected)
return true;
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms);
while (m_status != expected)
while (m_state != expected)
{
if (m_cv.wait_until(lk, deadline) == std::cv_status::timeout)
return false;
@@ -141,13 +145,13 @@ public:
private:
mutable std::mutex m_mtx;
std::condition_variable m_cv;
sdv::ipc::EConnectStatus m_status{ sdv::ipc::EConnectStatus::uninitialized };
sdv::ipc::EConnectState m_state{ sdv::ipc::EConnectState::uninitialized };
sdv::sequence<sdv::pointer<uint8_t>> m_lastData;
bool m_received{ false };
};
// A receiver that intentionally throws from SetStatus(...) to test callback-safety.
// A receiver that intentionally throws from SetConnectState(...) to test callback-safety.
class CUDSThrowingReceiver :
public sdv::IInterfaceAccess,
public sdv::ipc::IDataReceiveCallback,
@@ -161,9 +165,9 @@ public:
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>& /*seq*/) override {}
void SetStatus(sdv::ipc::EConnectStatus s) override
void SetConnectState(sdv::ipc::EConnectState s) override
{
// Store the last status and then throw to simulate misbehaving user code.
// Store the last state and then throw to simulate misbehaving user code.
{
std::lock_guard<std::mutex> lk(m_mtx);
m_last = s;
@@ -172,7 +176,7 @@ public:
throw std::runtime_error("Intentional user callback failure");
}
bool WaitForStatus(sdv::ipc::EConnectStatus expected, uint32_t ms = 2000)
bool WaitForState(sdv::ipc::EConnectState expected, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
@@ -193,7 +197,7 @@ public:
private:
std::mutex m_mtx;
std::condition_variable m_cv;
sdv::ipc::EConnectStatus m_last{ sdv::ipc::EConnectStatus::uninitialized };
sdv::ipc::EConnectState m_last{ sdv::ipc::EConnectState::uninitialized };
};
@@ -328,7 +332,7 @@ TEST(UnixSocketIPC, BasicConnectDisconnect)
if (!clientConn->WaitForConnection(5000)) { clientResult = 4; return; }
if (clientConn->GetStatus() != sdv::ipc::EConnectStatus::connected) { clientResult = 5; return; }
if (clientConn->GetConnectState() != sdv::ipc::EConnectState::connected) { clientResult = 5; return; }
// Wait for the server to be conected before disconecting the client
while (!allowClientDisconnect.load())
@@ -339,7 +343,7 @@ TEST(UnixSocketIPC, BasicConnectDisconnect)
// SERVER must also report connected
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::connected);
// Allow client to dissconect now, because the Server is connected
allowClientDisconnect = true;
@@ -348,7 +352,7 @@ TEST(UnixSocketIPC, BasicConnectDisconnect)
//DISCONNECT both
serverConn->Disconnect();
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::disconnected);
// Shutdown Manager / Framework
EXPECT_NO_THROW(mgr.Shutdown());
@@ -403,7 +407,7 @@ TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath)
if (!clientConn->AsyncConnect(&cRcvr)) { clientResult = 3; return; }
if (!clientConn->WaitForConnection(5000)) { clientResult = 4; return; }
if (clientConn->GetStatus() != sdv::ipc::EConnectStatus::connected) { clientResult = 5; return; }
if (clientConn->GetConnectState() != sdv::ipc::EConnectState::connected) { clientResult = 5; return; }
//waits for confirmation before disconect
while (!allowClientDisconnect.load())
@@ -415,7 +419,7 @@ TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath)
// 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);
EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::connected);
// Allows Client to disconnect and waits for finishing
allowClientDisconnect = true;
@@ -424,7 +428,7 @@ TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath)
// Disconnect server
serverConn->Disconnect();
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::disconnected);
// SESSION 2
// SERVER
@@ -450,7 +454,7 @@ TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath)
if (!clientConn2->AsyncConnect(&cRcvr2)) { clientResult2 = 3; return; }
if (!clientConn2->WaitForConnection(5000)) { clientResult2 = 4; return; }
if (clientConn2->GetStatus() != sdv::ipc::EConnectStatus::connected) { clientResult2 = 5; return; }
if (clientConn2->GetConnectState() != sdv::ipc::EConnectState::connected) { clientResult2 = 5; return; }
//waits for confirmation before disconect
while (!allowClientDisconnect2.load())
@@ -463,7 +467,7 @@ TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath)
// 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);
EXPECT_EQ(serverConn2->GetConnectState(), sdv::ipc::EConnectState::connected);
// Allows Client to disconnect and waits for finishing
allowClientDisconnect2 = true;
@@ -472,7 +476,7 @@ TEST(UnixSocketIPC, ReconnectAfterDisconnect_SamePath)
// Disconnect server
serverConn2->Disconnect();
EXPECT_EQ(serverConn2->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(serverConn2->GetConnectState(), sdv::ipc::EConnectState::disconnected);
//Shutdown manager/framework
EXPECT_NO_THROW(mgr.Shutdown());
@@ -550,8 +554,8 @@ TEST(UnixSocketIPC, CreateEndpoint_WithConfigAndPathClamping)
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);
EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(clientConn->GetConnectState(), sdv::ipc::EConnectState::connected);
// Cleanup
clientConn->Disconnect();
@@ -592,8 +596,8 @@ TEST(UnixSocketIPC, Access_DefaultPath_ServerClientConnect)
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);
EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::connected);
EXPECT_EQ(clientConn->GetConnectState(), sdv::ipc::EConnectState::connected);
clientConn->Disconnect();
serverConn->Disconnect();
@@ -637,7 +641,7 @@ TEST(UnixSocketIPC, WaitForConnection_InfiniteWait_SlowClient)
// INFINITE wait (0xFFFFFFFFu)
EXPECT_TRUE(serverConn->WaitForConnection(0xFFFFFFFFu));
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::connected);
EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::connected);
// Cleanup
delayedClient.join();
@@ -691,7 +695,8 @@ TEST(UnixSocketIPC, WaitForConnection_ZeroTimeout_BeforeAndAfter)
TEST(UnixSocketIPC, ClientTimeout_NoServer)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
@@ -714,7 +719,7 @@ TEST(UnixSocketIPC, ClientTimeout_NoServer)
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);
EXPECT_EQ(clientConn->GetConnectState(), sdv::ipc::EConnectState::connection_error);
clientConn->Disconnect(); // cleanup (joins threads)
EXPECT_NO_THROW(mgr.Shutdown());
@@ -754,9 +759,9 @@ TEST(UnixSocketIPC, ServerDisconnectPropagatesToClient)
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_TRUE(cRcvr.WaitForState(sdv::ipc::EConnectState::disconnected, /*ms*/ 3000)) << "Client did not observe 'disconnected' after server closed the socket.";
EXPECT_EQ(clientConn->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(clientConn->GetConnectState(), sdv::ipc::EConnectState::disconnected);
clientConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
@@ -794,7 +799,7 @@ TEST(UnixSocketIPC, ReconnectOnSameServerInstance)
ASSERT_TRUE(clientConn->WaitForConnection(5000));
clientConn->Disconnect();
serverConn->Disconnect();
EXPECT_EQ(serverConn->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::disconnected);
}
// Second session on the same serverConn object
@@ -1129,7 +1134,7 @@ TEST(UnixSocketIPC, PeerCloseMidTransfer_ClientSeesDisconnected_AndSendMayFailOr
std::cout << "[Debug] SendData result = " << sendResult.load() << std::endl;
// But client MUST observe disconnected:
EXPECT_TRUE(cRcvr.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 3000));
EXPECT_TRUE(cRcvr.WaitForState(sdv::ipc::EConnectState::disconnected, 3000));
clientConn->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
@@ -1162,8 +1167,8 @@ TEST(UnixSocketIPC, ClientCancelConnect_NoServer_CleansUpPromptly)
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);
// Immediately after disconnect, state should be 'disconnected' (no hangs).
EXPECT_EQ(clientConn->GetConnectState(), sdv::ipc::EConnectState::disconnected);
EXPECT_NO_THROW(mgr.Shutdown()); app.Shutdown();
}
@@ -1172,7 +1177,8 @@ TEST(UnixSocketIPC, ClientCancelConnect_NoServer_CleansUpPromptly)
TEST(UnixSocketIPC, ServerStartThenImmediateDisconnect_NoClient)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
app.SetRunningMode();
CUnixDomainSocketsChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
@@ -1192,15 +1198,15 @@ TEST(UnixSocketIPC, ServerStartThenImmediateDisconnect_NoClient)
// 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_EQ(serverConn->GetConnectState(), sdv::ipc::EConnectState::disconnected);
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//Callback throws in SetStatus -> transport threads keep running
TEST(UnixSocketIPC, CallbackThrowsInSetStatus_DoesNotCrashTransport)
//Callback throws in SetConnectState -> transport threads keep running
TEST(UnixSocketIPC, CallbackThrowsInSetConnectState_DoesNotCrashTransport)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
@@ -1227,7 +1233,7 @@ TEST(UnixSocketIPC, CallbackThrowsInSetStatus_DoesNotCrashTransport)
CUDSThrowingReceiver cRcvr;
ASSERT_TRUE(clientConn->AsyncConnect(&cRcvr));
// Despite exceptions thrown inside SetStatus, the transport should still reach connected.
// Despite exceptions thrown inside SetConnectState, the transport should still reach connected.
EXPECT_TRUE(serverConn->WaitForConnection(5000));
EXPECT_TRUE(clientConn->WaitForConnection(5000));
@@ -1239,8 +1245,8 @@ TEST(UnixSocketIPC, CallbackThrowsInSetStatus_DoesNotCrashTransport)
app.Shutdown();
}
//RegisterStatusEventCallback: multiple listeners receive status updates
TEST(UnixSocketIPC, RegisterStatusEventCallback_MultipleCallbacksReceiveStatus)
//RegisterStateEventCallback: multiple listeners receive state updates
TEST(UnixSocketIPC, RegisterStateEventCallback_MultipleCallbacksReceiveState)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
@@ -1266,12 +1272,12 @@ TEST(UnixSocketIPC, RegisterStatusEventCallback_MultipleCallbacksReceiveStatus)
// --- Callback receiver 1 ---
CUDSConnectReceiver recv1;
uint64_t cookie1 = server->RegisterStatusEventCallback(&recv1);
uint64_t cookie1 = server->RegisterStateEventCallback(&recv1);
EXPECT_NE(cookie1, 0u);
// --- Callback receiver 2 ---
CUDSConnectReceiver recv2;
uint64_t cookie2 = server->RegisterStatusEventCallback(&recv2);
uint64_t cookie2 = server->RegisterStateEventCallback(&recv2);
EXPECT_NE(cookie2, 0u);
EXPECT_NE(cookie1, cookie2);
@@ -1283,22 +1289,22 @@ TEST(UnixSocketIPC, RegisterStatusEventCallback_MultipleCallbacksReceiveStatus)
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));
EXPECT_TRUE(recv1.WaitForState(sdv::ipc::EConnectState::connected, 1000));
EXPECT_TRUE(recv2.WaitForState(sdv::ipc::EConnectState::connected, 1000));
// --- Disconnect ---
client->Disconnect();
EXPECT_TRUE(recv1.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 1000));
EXPECT_TRUE(recv2.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 1000));
EXPECT_TRUE(recv1.WaitForState(sdv::ipc::EConnectState::disconnected, 1000));
EXPECT_TRUE(recv2.WaitForState(sdv::ipc::EConnectState::disconnected, 1000));
server->Disconnect();
EXPECT_NO_THROW(mgr.Shutdown());
app.Shutdown();
}
//UnregisterStatusEventCallback: removed listener stops receiving events
TEST(UnixSocketIPC, UnregisterStatusEventCallback_RemovedListenerStopsReceiving)
//UnregisterStateEventCallback: removed listener stops receiving events
TEST(UnixSocketIPC, UnregisterStateEventCallback_RemovedListenerStopsReceiving)
{
// Framework + Manager
sdv::app::CAppControl app;
@@ -1328,13 +1334,13 @@ TEST(UnixSocketIPC, UnregisterStatusEventCallback_RemovedListenerStopsReceiving)
ASSERT_NE(client, nullptr);
// --------- Two distinct receivers ----------
// recvReg: used ONLY for the status-callback registry
// recvReg: used ONLY for the state-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);
// Register recvReg as a state listener (registry path)
uint64_t cookie = server->RegisterStateEventCallback(&recvReg);
ASSERT_NE(cookie, 0u) << "Cookie must be non-zero";
// Start connections (server uses recvReg only for registry; recvConn is the IConnect/IDataReceive side)
@@ -1346,21 +1352,21 @@ TEST(UnixSocketIPC, UnregisterStatusEventCallback_RemovedListenerStopsReceiving)
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'.";
EXPECT_TRUE(recvConn.WaitForState(sdv::ipc::EConnectState::connected, 1000)) << "Connection receiver didn't see 'connected'.";
EXPECT_TRUE(recvReg.WaitForState(sdv::ipc::EConnectState::connected, 1000)) << "Registry listener didn't see 'connected'.";
// --------- Unregister the registry listener ----------
server->UnregisterStatusEventCallback(cookie);
server->UnregisterStateEventCallback(cookie);
// Trigger a disconnect on client to force status transitions
// Trigger a disconnect on client to force state 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'.";
EXPECT_TRUE(recvConn.WaitForState(sdv::ipc::EConnectState::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().";
EXPECT_FALSE(recvReg.WaitForState(sdv::ipc::EConnectState::disconnected, 300)) << "Registry listener received 'disconnected' after UnregisterStateEventCallback().";
// Server cleanup
server->Disconnect();

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__)

View File

@@ -7,7 +7,7 @@
#
# SPDX-License-Identifier: Apache-2.0
#*******************************************************************************
if(WIN32)
cmake_minimum_required(VERSION 3.20)
project(WinSocketCommunicationTests LANGUAGES CXX)
@@ -36,6 +36,7 @@ target_include_directories(UnitTest_WinSocketConnectTests
Ws2_32
Winmm
Rpcrt4
uds_win_sockets
)
add_test(NAME UnitTest_WinSocketConnectTests
@@ -50,4 +51,6 @@ add_custom_command(TARGET UnitTest_WinSocketConnectTests POST_BUILD
--gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinSocketConnectTests.xml
VERBATIM
)
endif()
endif()

View File

@@ -15,8 +15,8 @@
#include <support/app_control.h>
#include <interfaces/ipc.h>
#include "../../../sdv_services/uds_win_sockets/channel_mgnt.cpp"
#include "../../../sdv_services/uds_win_sockets/connection.cpp"
#include "../../../sdv_services/uds_win_sockets/channel_mgnt.h"
#include "../../../sdv_services/uds_win_sockets/connection.h"
#include <windows.h>
#include <afunix.h>
@@ -99,7 +99,7 @@ inline void SpinUntilServerArmed(sdv::ipc::IConnect* server, uint32_t maxWaitMs
using namespace std::chrono;
auto deadline = steady_clock::now() + milliseconds(maxWaitMs);
while (server->GetStatus() == sdv::ipc::EConnectStatus::uninitialized &&
while (server->GetConnectState() == sdv::ipc::EConnectState::uninitialized &&
steady_clock::now() < deadline)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
@@ -116,7 +116,7 @@ inline void SleepTiny(uint32_t ms = 20)
} // namespace test_utils
// Unified test receiver (status + data)
// Unified test receiver (state + data)
class CTestReceiver :
public sdv::IInterfaceAccess,
public sdv::ipc::IConnectEventCallback,
@@ -128,11 +128,11 @@ public:
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
END_SDV_INTERFACE_MAP()
void SetStatus(sdv::ipc::EConnectStatus s) override
void SetConnectState(sdv::ipc::EConnectState s) override
{
{
std::lock_guard<std::mutex> lk(m_mtx);
m_status = s;
m_state = s;
}
m_cv.notify_all();
}
@@ -147,11 +147,11 @@ public:
m_cv.notify_all();
}
bool WaitForStatus(sdv::ipc::EConnectStatus expected, uint32_t ms = 2000)
bool WaitForState(sdv::ipc::EConnectState expected, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
[&]{ return m_status == expected; });
[&]{ return m_state == expected; });
}
bool WaitForData(uint32_t ms = 2000)
@@ -171,7 +171,7 @@ private:
mutable std::mutex m_mtx;
std::condition_variable m_cv;
sdv::ipc::EConnectStatus m_status{ sdv::ipc::EConnectStatus::uninitialized };
sdv::ipc::EConnectState m_state{ sdv::ipc::EConnectState::uninitialized };
sdv::sequence<sdv::pointer<uint8_t>> m_data;
bool m_hasData{false};
};
@@ -344,7 +344,7 @@ TEST(WindowsAFUnixIPC, ServerDisconnectPropagates)
pair.server->Disconnect();
EXPECT_TRUE(cr.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 3000));
EXPECT_TRUE(cr.WaitForState(sdv::ipc::EConnectState::disconnected, 3000));
pair.client->Disconnect();
@@ -713,7 +713,8 @@ TEST(WindowsAFUnixIPC, WaitForConnection_ZeroTimeout_BeforeAndAfter)
TEST(WindowsAFUnixIPC, ClientTimeout_NoServer)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
app.SetRunningMode();
CSocketsChannelMgnt mgr;
@@ -742,7 +743,7 @@ TEST(WindowsAFUnixIPC, ClientTimeout_NoServer)
EXPECT_FALSE(client->WaitForConnection(1500));
std::this_thread::sleep_for(std::chrono::milliseconds(800));
EXPECT_EQ(client->GetStatus(), sdv::ipc::EConnectStatus::connection_error);
EXPECT_EQ(client->GetConnectState(), sdv::ipc::EConnectState::connection_error);
client->Disconnect();
mgr.Shutdown();
@@ -799,7 +800,7 @@ TEST(WindowsAFUnixIPC, PeerCloseMidTransfer_ClientDetectsDisconnect)
t.join();
EXPECT_TRUE(cr.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 3000));
EXPECT_TRUE(cr.WaitForState(sdv::ipc::EConnectState::disconnected, 3000));
pair.client->Disconnect();
mgr.Shutdown();
@@ -810,7 +811,8 @@ TEST(WindowsAFUnixIPC, PeerCloseMidTransfer_ClientDetectsDisconnect)
TEST(WindowsAFUnixIPC, ClientCancelConnect_NoServer_Cleanup)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
app.SetRunningMode();
CSocketsChannelMgnt mgr;
@@ -838,7 +840,7 @@ TEST(WindowsAFUnixIPC, ClientCancelConnect_NoServer_Cleanup)
std::this_thread::sleep_for(std::chrono::milliseconds(150));
client->Disconnect();
EXPECT_EQ(client->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(client->GetConnectState(), sdv::ipc::EConnectState::disconnected);
mgr.Shutdown();
app.Shutdown();
@@ -848,7 +850,8 @@ TEST(WindowsAFUnixIPC, ClientCancelConnect_NoServer_Cleanup)
TEST(WindowsAFUnixIPC, ServerStartThenImmediateDisconnect_NoClient)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
app.SetRunningMode();
CSocketsChannelMgnt mgr;
@@ -872,14 +875,14 @@ TEST(WindowsAFUnixIPC, ServerStartThenImmediateDisconnect_NoClient)
std::this_thread::sleep_for(std::chrono::milliseconds(50));
server->Disconnect();
EXPECT_EQ(server->GetStatus(), sdv::ipc::EConnectStatus::disconnected);
EXPECT_EQ(server->GetConnectState(), sdv::ipc::EConnectState::disconnected);
mgr.Shutdown();
app.Shutdown();
}
// UnregisterStatusEventCallback: ensure single-listener semantics
TEST(WindowsAFUnixIPC, UnregisterStatusEventCallback_SingleListenerSemantics)
// UnregisterStateEventCallback: ensure single-listener semantics
TEST(WindowsAFUnixIPC, UnregisterStateEventCallback_SingleListenerSemantics)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
@@ -905,7 +908,7 @@ TEST(WindowsAFUnixIPC, UnregisterStatusEventCallback_SingleListenerSemantics)
ASSERT_NE(client, nullptr);
CTestReceiver regListener;
const uint64_t cookie = server->RegisterStatusEventCallback(&regListener);
const uint64_t cookie = server->RegisterStateEventCallback(&regListener);
ASSERT_NE(cookie, 0u);
CTestReceiver mainRecv;
@@ -917,15 +920,15 @@ TEST(WindowsAFUnixIPC, UnregisterStatusEventCallback_SingleListenerSemantics)
EXPECT_TRUE(server->WaitForConnection(5000));
EXPECT_TRUE(client->WaitForConnection(5000));
EXPECT_TRUE(mainRecv.WaitForStatus(sdv::ipc::EConnectStatus::connected, 1000));
EXPECT_FALSE(regListener.WaitForStatus(sdv::ipc::EConnectStatus::connected, 300)) << "The registry listener should NOT receive events while main receiver is active.";
EXPECT_TRUE(mainRecv.WaitForState(sdv::ipc::EConnectState::connected, 1000));
EXPECT_FALSE(regListener.WaitForState(sdv::ipc::EConnectState::connected, 300)) << "The registry listener should NOT receive events while main receiver is active.";
server->UnregisterStatusEventCallback(cookie);
server->UnregisterStateEventCallback(cookie);
client->Disconnect();
EXPECT_TRUE(mainRecv.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 1500));
EXPECT_TRUE(mainRecv.WaitForState(sdv::ipc::EConnectState::disconnected, 1500));
EXPECT_FALSE(regListener.WaitForStatus(sdv::ipc::EConnectStatus::disconnected, 300));
EXPECT_FALSE(regListener.WaitForState(sdv::ipc::EConnectState::disconnected, 300));
server->Disconnect();
mgr.Shutdown();

View File

@@ -0,0 +1,134 @@
#*******************************************************************************
# 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(WIN32)
cmake_minimum_required(VERSION 3.20)
project(WinTunnelCommunicationTests LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Executable: GTest for UDS connect
add_executable(UnitTest_WinTunnelConnectTests
win_tunnel_connect_tests.cpp
)
target_include_directories(UnitTest_WinTunnelConnectTests
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/export
${PROJECT_SOURCE_DIR}/export/support
${PROJECT_SOURCE_DIR}/export/interfaces
${PROJECT_SOURCE_DIR}/sdv_services
)
target_link_libraries(UnitTest_WinTunnelConnectTests
PRIVATE
GTest::GTest
Ws2_32
Winmm
Rpcrt4
uds_win_tunnel
uds_win_sockets
)
add_test(NAME UnitTest_WinTunnelConnectTests
COMMAND UnitTest_WinTunnelConnectTests)
add_dependencies(UnitTest_WinTunnelConnectTests dependency_sdv_components)
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
add_custom_command(TARGET UnitTest_WinTunnelConnectTests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake
"$<TARGET_FILE:UnitTest_WinTunnelConnectTests>"
--gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinTunnelConnectTests.xml
VERBATIM
)
endif()
add_executable(UnitTest_WinTunnelChannelMgntTests
win_tunnel_channel_mgnt_tests.cpp
)
target_include_directories(UnitTest_WinTunnelChannelMgntTests
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/export
${PROJECT_SOURCE_DIR}/export/support
${PROJECT_SOURCE_DIR}/export/interfaces
${PROJECT_SOURCE_DIR}/sdv_services
)
target_link_libraries(UnitTest_WinTunnelChannelMgntTests
PRIVATE
GTest::GTest
Ws2_32
Winmm
Rpcrt4
uds_win_tunnel
uds_win_sockets
)
add_test(NAME UnitTest_WinTunnelChannelMgntTests
COMMAND UnitTest_WinTunnelChannelMgntTests)
add_dependencies(UnitTest_WinTunnelChannelMgntTests dependency_sdv_components)
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
add_custom_command(TARGET UnitTest_WinTunnelChannelMgntTests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake
"$<TARGET_FILE:UnitTest_WinTunnelChannelMgntTests>"
--gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinTunnelChannelMgntTests.xml
VERBATIM
)
endif()
# Add negative/edge case tests
add_executable(UnitTest_WinTunnelNegativeEdgeTests
win_tunnel_negative_edge_tests.cpp
)
target_include_directories(UnitTest_WinTunnelNegativeEdgeTests
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/export
${PROJECT_SOURCE_DIR}/export/support
${PROJECT_SOURCE_DIR}/export/interfaces
${PROJECT_SOURCE_DIR}/sdv_services
)
target_link_libraries(UnitTest_WinTunnelNegativeEdgeTests
PRIVATE
GTest::GTest
Ws2_32
Winmm
Rpcrt4
uds_win_tunnel
uds_win_sockets
)
add_test(NAME UnitTest_WinTunnelNegativeEdgeTests
COMMAND UnitTest_WinTunnelNegativeEdgeTests)
add_dependencies(UnitTest_WinTunnelNegativeEdgeTests dependency_sdv_components)
if ((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (NOT WIN32))
add_custom_command(TARGET UnitTest_WinTunnelNegativeEdgeTests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E env TEST_EXECUTION_MODE=CMake
"$<TARGET_FILE:UnitTest_WinTunnelNegativeEdgeTests>"
--gtest_output=xml:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/UnitTest_WinTunnelNegativeEdgeTests.xml
VERBATIM
)
endif()
endif()

View File

@@ -0,0 +1,442 @@
/********************************************************************************
* 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
********************************************************************************/
#include "gtest/gtest.h"
#include <support/app_control.h>
#include <interfaces/ipc.h>
#include "../../../sdv_services/uds_win_tunnel/channel_mgnt.h"
#include "../../../sdv_services/uds_win_tunnel/connection.h"
// Reuse helpers from uds_win_sockets (path normalization, MakeShortUdsPath)
#include "../../../sdv_services/uds_win_sockets/channel_mgnt.h"
#include "../../../sdv_services/uds_win_sockets/connection.h"
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <random>
#include <sstream>
#include <cstring>
#include <windows.h>
#include <afunix.h>
// Helper namespace
namespace tunnel_utils
{
inline std::string Expand(const std::string& in)
{
if (in.find('%') == std::string::npos)
return in;
char buf[4096] = {};
const DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, sizeof(buf));
return (n > 0 && n < sizeof(buf)) ? std::string(buf) : in;
}
inline void EnsureParentDir(const std::string& full)
{
auto p = full.find_last_of("\\/");
if (p == std::string::npos)
return;
std::string dir = full.substr(0, p);
CreateDirectoryA(dir.c_str(), nullptr);
}
inline std::string MakeShortUdsPath(const char* name)
{
std::string base = R"(%LOCALAPPDATA%\sdv\)";
base = Expand(base);
EnsureParentDir(base);
return base + name;
}
inline std::string RandomHex()
{
std::mt19937_64 r{std::random_device{}()};
std::uniform_int_distribution<uint64_t> d;
std::ostringstream oss;
oss << std::hex << d(r);
return oss.str();
}
inline std::string Unique(const char* prefix)
{
return MakeShortUdsPath((std::string(prefix) + "_" + RandomHex() + ".sock").c_str());
}
inline void SpinUntilServerArmed(sdv::ipc::IConnect* server)
{
using namespace std::chrono;
const auto deadline = steady_clock::now() + milliseconds(500);
while (server->GetConnectState() == sdv::ipc::EConnectState::uninitialized &&
steady_clock::now() < deadline)
{
std::this_thread::sleep_for(milliseconds(2));
}
}
} // namespace tunnel_utils
using namespace tunnel_utils;
// Unified receiver (state + data)
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 SetConnectState(sdv::ipc::EConnectState s) override
{
{ std::lock_guard<std::mutex> lk(m_mtx); m_state = s; }
m_cv.notify_all();
}
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>& seq) override
{
{ std::lock_guard<std::mutex> lk(m_mtx);
m_last = seq; m_has = true; }
m_cv.notify_all();
}
bool WaitForState(sdv::ipc::EConnectState s, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
[&]{ return m_state == s; });
}
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_has; });
}
sdv::sequence<sdv::pointer<uint8_t>> Data()
{
std::lock_guard<std::mutex> lk(m_mtx);
return m_last;
}
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_last;
bool m_has{false};
};
// Helper to create server + client
struct TunnelPair
{
sdv::TObjectPtr serverObj;
sdv::ipc::IConnect* server = nullptr;
sdv::TObjectPtr clientObj;
sdv::ipc::IConnect* client = nullptr;
};
static TunnelPair CreateTunnelPair(CSocketsTunnelChannelMgnt& mgr,
const std::string& cs)
{
TunnelPair out;
out.serverObj = mgr.Access(cs);
out.server = out.serverObj ?
out.serverObj.GetInterface<sdv::ipc::IConnect>() : nullptr;
out.clientObj = mgr.Access(cs);
out.client = out.clientObj ?
out.clientObj.GetInterface<sdv::ipc::IConnect>() : nullptr;
return out;
}
// TESTS
// Manager instantiate + lifecycle
TEST(WinTunnelChannelMgnt, InstantiateAndLifecycle)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
CSocketsTunnelChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized);
mgr.SetOperationMode(sdv::EOperationMode::configuring);
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::configuring);
mgr.SetOperationMode(sdv::EOperationMode::running);
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::running);
mgr.Shutdown();
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);
app.Shutdown();
}
// Basic connect/disconnect using manager (server + client)
TEST(WinTunnelChannelMgnt, BasicConnectDisconnect)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
const std::string uds = Unique("tunnel_mgr_basic");
const std::string cs = "proto=tunnel;path=" + uds + ";";
auto ep = mgr.CreateEndpoint(cs);
ASSERT_FALSE(ep.ssConnectString.empty());
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
ASSERT_NE(pair.server, nullptr);
ASSERT_NE(pair.client, nullptr);
CTunnelMgrTestReceiver sr, cr;
pair.server->AsyncConnect(&sr);
SpinUntilServerArmed(pair.server);
pair.client->AsyncConnect(&cr);
EXPECT_TRUE(pair.server->WaitForConnection(5000));
EXPECT_TRUE(pair.client->WaitForConnection(5000));
pair.client->Disconnect();
pair.server->Disconnect();
mgr.Shutdown();
app.Shutdown();
}
// Simple hello (header stripped)
TEST(WinTunnelChannelMgnt, DataPath_SimpleHello_ViaManager)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
const std::string cs = "proto=tunnel;path=" + Unique("hello_mgr") + ";";
auto ep = mgr.CreateEndpoint(cs);
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
ASSERT_NE(pair.server, nullptr);
ASSERT_NE(pair.client, nullptr);
CTunnelMgrTestReceiver sr, cr;
pair.server->AsyncConnect(&sr);
SpinUntilServerArmed(pair.server);
pair.client->AsyncConnect(&cr);
ASSERT_TRUE(pair.server->WaitForConnection(5000));
ASSERT_TRUE(pair.client->WaitForConnection(5000));
sdv::pointer<uint8_t> msg;
msg.resize(5);
memcpy(msg.get(), "hello", 5);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(msg);
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
ASSERT_NE(sender, nullptr);
EXPECT_TRUE(sender->SendData(seq));
ASSERT_TRUE(sr.WaitForData(3000));
auto recv = sr.Data();
ASSERT_EQ(recv.size(), 1u);
ASSERT_EQ(recv[0].size(), 5u);
EXPECT_EQ(memcmp(recv[0].get(), "hello", 5), 0);
pair.client->Disconnect();
pair.server->Disconnect();
mgr.Shutdown();
app.Shutdown();
}
// Multi-chunk
TEST(WinTunnelChannelMgnt, DataPath_MultiChunk_ViaManager)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
const std::string cs = "proto=tunnel;path=" + Unique("mc_mgr") + ";";
auto ep = mgr.CreateEndpoint(cs);
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
ASSERT_NE(pair.server, nullptr);
ASSERT_NE(pair.client, nullptr);
CTunnelMgrTestReceiver sr, cr;
pair.server->AsyncConnect(&sr);
SpinUntilServerArmed(pair.server);
pair.client->AsyncConnect(&cr);
ASSERT_TRUE(pair.server->WaitForConnection(5000));
ASSERT_TRUE(pair.client->WaitForConnection(5000));
sdv::pointer<uint8_t> a, b;
a.resize(3);
memcpy(a.get(), "sdv", 3);
b.resize(9);
memcpy(b.get(), "framework", 9);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(a);
seq.push_back(b);
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
ASSERT_NE(sender, nullptr);
EXPECT_TRUE(sender->SendData(seq));
ASSERT_TRUE(sr.WaitForData(3000));
auto recv = sr.Data();
ASSERT_EQ(recv.size(), 2u);
EXPECT_EQ(memcmp(recv[0].get(), "sdv", 3), 0);
EXPECT_EQ(memcmp(recv[1].get(), "framework", 9), 0);
pair.client->Disconnect();
pair.server->Disconnect();
mgr.Shutdown();
app.Shutdown();
}
// Header stripping invariant
TEST(WinTunnelChannelMgnt, HeaderStrippedInvariant)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
const std::string cs ="proto=tunnel;path=" + Unique("hdr_mgr") + ";";
auto ep = mgr.CreateEndpoint(cs);
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
ASSERT_NE(pair.server, nullptr);
ASSERT_NE(pair.client, nullptr);
CTunnelMgrTestReceiver sr, cr;
pair.server->AsyncConnect(&sr);
SpinUntilServerArmed(pair.server);
pair.client->AsyncConnect(&cr);
ASSERT_TRUE(pair.server->WaitForConnection(5000));
ASSERT_TRUE(pair.client->WaitForConnection(5000));
const char* msg = "HDR_TEST";
const size_t len = strlen(msg);
sdv::pointer<uint8_t> buf;
buf.resize(len);
memcpy(buf.get(), msg, len);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(buf);
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
ASSERT_NE(sender, nullptr);
EXPECT_TRUE(sender->SendData(seq));
ASSERT_TRUE(sr.WaitForData(3000));
auto recv = sr.Data();
ASSERT_EQ(recv.size(), 1u);
ASSERT_EQ(recv[0].size(), len);
EXPECT_EQ(memcmp(recv[0].get(), msg, len), 0);
pair.client->Disconnect();
pair.server->Disconnect();
mgr.Shutdown();
app.Shutdown();
}
// Long path normalization
TEST(WinTunnelChannelMgnt, CreateEndpoint_LongPath_Normalized)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
std::string veryLong(200, 'A');
const std::string raw ="C:\\Users\\" + veryLong + "\\AppData\\Local\\sdv\\tunnel_long_" + RandomHex() + ".sock";
const std::string cs = "proto=tunnel;path=" + raw + ";";
auto ep = mgr.CreateEndpoint(cs);
ASSERT_FALSE(ep.ssConnectString.empty());
// path must be normalized (basename only)
EXPECT_NE(ep.ssConnectString.find("path=tunnel_long_"), std::string::npos);
sdv::TObjectPtr obj = mgr.Access(ep.ssConnectString);
auto* server = obj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(server, nullptr);
CTunnelMgrTestReceiver sr;
server->AsyncConnect(&sr);
SpinUntilServerArmed(server);
// boot client
sdv::TObjectPtr cobj = mgr.Access(ep.ssConnectString);
auto* client = cobj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(client, nullptr);
CTunnelMgrTestReceiver cr;
client->AsyncConnect(&cr);
EXPECT_TRUE(server->WaitForConnection(5000));
EXPECT_TRUE(client->WaitForConnection(5000));
client->Disconnect();
server->Disconnect();
mgr.Shutdown();
app.Shutdown();
}

View File

@@ -0,0 +1,454 @@
/********************************************************************************
* 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
********************************************************************************/
#include "gtest/gtest.h"
#include <support/app_control.h>
#include <interfaces/ipc.h>
// Include transport and tunnel manager
#include "../../../sdv_services/uds_win_tunnel/channel_mgnt.h"
#include "../../../sdv_services/uds_win_tunnel/connection.h"
// Include UDS Windows helpers for path generation
#include "../../../sdv_services/uds_win_sockets/channel_mgnt.h" // ONLY FOR path helpers (MakeShortUdsPath)
#include "../../../sdv_services/uds_win_sockets/connection.h"
#include <windows.h>
#include <afunix.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstring>
#include <mutex>
#include <random>
#include <sstream>
#include <string>
#include <thread>
// Utility namespace from original tests
namespace test_utils
{
inline std::string Expand(const std::string& in)
{
if (in.find('%') == std::string::npos)
return in;
char buf[4096] = {};
DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, sizeof(buf));
return (n > 0 && n < sizeof(buf)) ? std::string(buf) : in;
}
inline void EnsureParentDir(const std::string& fullPath)
{
auto pos = fullPath.find_last_of("\\/");
if (pos == std::string::npos)
return;
std::string dir = fullPath.substr(0, pos);
CreateDirectoryA(dir.c_str(), nullptr);
}
inline std::string MakeShortUdsPath(const char* name)
{
std::string base = R"(%LOCALAPPDATA%\sdv\)";
base = Expand(base);
EnsureParentDir(base);
return base + name;
}
inline std::string RandomHex()
{
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();
}
inline std::string UniqueUds(const char* prefix)
{
return MakeShortUdsPath((std::string(prefix) + "_" + RandomHex() + ".sock").c_str());
}
inline void SpinUntilServerArmed(sdv::ipc::IConnect* server, uint32_t maxWaitMs = 300)
{
using namespace std::chrono;
auto deadline = steady_clock::now() + milliseconds(maxWaitMs);
while (server->GetConnectState() == sdv::ipc::EConnectState::uninitialized &&
steady_clock::now() < deadline)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
inline void SleepTiny(uint32_t ms = 20)
{
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
} // namespace test_utils
using namespace test_utils;
// Unified test receiver (status + payload), identical to UDS tests
class CTunnelTestReceiver :
public sdv::IInterfaceAccess,
public sdv::ipc::IConnectEventCallback,
public sdv::ipc::IDataReceiveCallback
{
public:
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
END_SDV_INTERFACE_MAP()
void SetConnectState(sdv::ipc::EConnectState s) override
{
{
std::lock_guard<std::mutex> lk(m_mtx);
m_state = s;
}
m_cv.notify_all();
}
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>& seq) override
{
{
std::lock_guard<std::mutex> lk(m_mtx);
m_data = seq;
m_hasData = true;
}
m_cv.notify_all();
}
bool WaitForStatus(sdv::ipc::EConnectState expected, uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
[&]{ return m_state == expected; });
}
bool WaitForData(uint32_t ms = 2000)
{
std::unique_lock<std::mutex> lk(m_mtx);
return m_cv.wait_for(lk, std::chrono::milliseconds(ms),
[&]{ return m_hasData; });
}
sdv::sequence<sdv::pointer<uint8_t>> Data()
{
std::lock_guard<std::mutex> lk(m_mtx);
return m_data;
}
private:
mutable std::mutex m_mtx;
std::condition_variable m_cv;
sdv::ipc::EConnectState m_state{sdv::ipc::EConnectState::uninitialized};
sdv::sequence<sdv::pointer<uint8_t>> m_data;
bool m_hasData{false};
};
// Helper to create server + client via manager
struct TunnelPair
{
sdv::TObjectPtr serverObj;
sdv::ipc::IConnect* server = nullptr;
sdv::TObjectPtr clientObj;
sdv::ipc::IConnect* client = nullptr;
};
static TunnelPair CreateTunnelPair(CSocketsTunnelChannelMgnt& mgr, const std::string& cs)
{
TunnelPair out;
out.serverObj = mgr.Access(cs);
out.server = out.serverObj ? out.serverObj.GetInterface<sdv::ipc::IConnect>() : nullptr;
out.clientObj = mgr.Access(cs);
out.client = out.clientObj ? out.clientObj.GetInterface<sdv::ipc::IConnect>() : nullptr;
return out;
}
// TEST SUITE START
// Instantiate manager
TEST(WinTunnelIPC, InstantiateManager)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
CSocketsTunnelChannelMgnt mgr;
EXPECT_NO_THROW(mgr.Initialize(""));
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::initialized);
EXPECT_NO_THROW(mgr.Shutdown());
EXPECT_EQ(mgr.GetObjectState(), sdv::EObjectState::destruction_pending);
app.Shutdown();
}
// Basic connect/disconnect
TEST(WinTunnelIPC, BasicConnectDisconnect)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
const std::string path = UniqueUds("tunnel_basic");
const std::string cs = "proto=tunnel;path=" + path + ";";
auto ep = mgr.CreateEndpoint(cs);
ASSERT_FALSE(ep.ssConnectString.empty());
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
ASSERT_NE(pair.server, nullptr);
ASSERT_NE(pair.client, nullptr);
CTunnelTestReceiver sr, cr;
pair.server->AsyncConnect(&sr);
SpinUntilServerArmed(pair.server);
pair.client->AsyncConnect(&cr);
EXPECT_TRUE(pair.server->WaitForConnection(5000));
EXPECT_TRUE(pair.client->WaitForConnection(5000));
pair.client->Disconnect();
pair.server->Disconnect();
mgr.Shutdown();
app.Shutdown();
}
// Simple data (hello)
TEST(WinTunnelIPC, DataPath_SimpleHello_Tunnel)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
const std::string cs = "proto=tunnel;path=" + UniqueUds("hello_t") + ";";
auto ep = mgr.CreateEndpoint(cs);
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
ASSERT_NE(pair.server, nullptr);
ASSERT_NE(pair.client, nullptr);
CTunnelTestReceiver sr, cr;
pair.server->AsyncConnect(&sr);
SpinUntilServerArmed(pair.server);
pair.client->AsyncConnect(&cr);
ASSERT_TRUE(pair.server->WaitForConnection(5000));
ASSERT_TRUE(pair.client->WaitForConnection(5000));
// Build payload = "hello"
sdv::pointer<uint8_t> p;
p.resize(5);
memcpy(p.get(), "hello", 5);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(p);
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
ASSERT_NE(sender, nullptr);
EXPECT_TRUE(sender->SendData(seq));
EXPECT_TRUE(sr.WaitForData(3000));
auto recv = sr.Data();
// header eliminated
ASSERT_EQ(recv.size(), 1u);
ASSERT_EQ(recv[0].size(), 5u);
EXPECT_EQ(memcmp(recv[0].get(), "hello", 5), 0);
pair.client->Disconnect();
pair.server->Disconnect();
mgr.Shutdown();
app.Shutdown();
}
// Multi-chunk
TEST(WinTunnelIPC, DataPath_MultiChunk_Tunnel)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
const std::string cs = "proto=tunnel;path=" + UniqueUds("mc_t") + ";";
auto ep = mgr.CreateEndpoint(cs);
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
ASSERT_NE(pair.server, nullptr);
ASSERT_NE(pair.client, nullptr);
CTunnelTestReceiver sr, cr;
pair.server->AsyncConnect(&sr);
SpinUntilServerArmed(pair.server);
pair.client->AsyncConnect(&cr);
ASSERT_TRUE(pair.server->WaitForConnection(5000));
ASSERT_TRUE(pair.client->WaitForConnection(5000));
sdv::pointer<uint8_t> p1, p2;
p1.resize(3);
memcpy(p1.get(), "sdv", 3);
p2.resize(9);
memcpy(p2.get(), "framework", 9);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(p1);
seq.push_back(p2);
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
ASSERT_NE(sender, nullptr);
EXPECT_TRUE(sender->SendData(seq));
ASSERT_TRUE(sr.WaitForData(3000));
auto recv = sr.Data();
ASSERT_EQ(recv.size(), 2u);
EXPECT_EQ(memcmp(recv[0].get(), "sdv", 3), 0);
EXPECT_EQ(memcmp(recv[1].get(), "framework", 9), 0);
pair.client->Disconnect();
pair.server->Disconnect();
mgr.Shutdown();
app.Shutdown();
}
// Large fragmentation
TEST(WinTunnelIPC, DataPath_LargePayload_Tunnel)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
const std::string cs = "proto=tunnel;path=" + UniqueUds("big_t") + ";";
auto ep = mgr.CreateEndpoint(cs);
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
ASSERT_NE(pair.server, nullptr);
ASSERT_NE(pair.client, nullptr);
CTunnelTestReceiver sr, cr;
pair.server->AsyncConnect(&sr);
SpinUntilServerArmed(pair.server);
pair.client->AsyncConnect(&cr);
ASSERT_TRUE(pair.server->WaitForConnection(5000));
ASSERT_TRUE(pair.client->WaitForConnection(5000));
const size_t N = 256 * 1024;
sdv::pointer<uint8_t> payload;
payload.resize(N);
for (size_t i = 0; i < N; ++i)
payload.get()[i] = uint8_t(i & 0xFF);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(payload);
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
ASSERT_NE(sender, nullptr);
EXPECT_TRUE(sender->SendData(seq));
ASSERT_TRUE(sr.WaitForData(5000));
auto recv = sr.Data();
ASSERT_EQ(recv.size(), 1u);
ASSERT_EQ(recv[0].size(), N);
EXPECT_EQ(memcmp(recv[0].get(), payload.get(), N), 0);
pair.client->Disconnect();
pair.server->Disconnect();
mgr.Shutdown();
app.Shutdown();
}
// Header stripping test (tunnel feature)
TEST(WinTunnelIPC, DataPath_HeaderStripped_Tunnel)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
app.SetRunningMode();
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
const std::string cs =
"proto=tunnel;path=" + UniqueUds("headerstrip") + ";";
auto ep = mgr.CreateEndpoint(cs);
auto pair = CreateTunnelPair(mgr, ep.ssConnectString);
ASSERT_NE(pair.server, nullptr);
ASSERT_NE(pair.client, nullptr);
CTunnelTestReceiver sr, cr;
pair.server->AsyncConnect(&sr);
SpinUntilServerArmed(pair.server);
pair.client->AsyncConnect(&cr);
ASSERT_TRUE(pair.server->WaitForConnection(5000));
ASSERT_TRUE(pair.client->WaitForConnection(5000));
const char* msg = "HEADER_TEST";
const size_t len = strlen(msg);
sdv::pointer<uint8_t> p;
p.resize(len);
memcpy(p.get(), msg, len);
sdv::sequence<sdv::pointer<uint8_t>> seq;
seq.push_back(p);
auto* sender = dynamic_cast<sdv::ipc::IDataSend*>(pair.client);
ASSERT_NE(sender, nullptr);
EXPECT_TRUE(sender->SendData(seq));
ASSERT_TRUE(sr.WaitForData(3000));
auto recv = sr.Data();
// check header stripped
ASSERT_EQ(recv.size(), 1u);
ASSERT_EQ(recv[0].size(), len);
EXPECT_EQ(memcmp(recv[0].get(), msg, len), 0);
pair.client->Disconnect();
pair.server->Disconnect();
mgr.Shutdown();
app.Shutdown();
}

View File

@@ -0,0 +1,164 @@
/********************************************************************************
* 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
********************************************************************************/
#include "gtest/gtest.h"
#include <support/app_control.h>
#include <interfaces/ipc.h>
#include "../../../sdv_services/uds_win_tunnel/channel_mgnt.h"
#include "../../../sdv_services/uds_win_tunnel/connection.h"
// Include UDS Windows helpers for path generation
#include "../../../sdv_services/uds_win_sockets/channel_mgnt.h" // ONLY FOR path helpers (MakeShortUdsPath)
#include "../../../sdv_services/uds_win_sockets/connection.h"
// Helper for unique UDS path
template<typename T=void>
static std::string UniqueUds(const char* prefix) {
char buf[64];
sprintf(buf, "%s_%08x.sock", prefix, rand());
return std::string("%LOCALAPPDATA%/sdv/") + buf;
}
// Negative: invalid connect string
TEST(WinTunnelNegative, InvalidConnectString)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
// Missing proto, missing path
auto obj = mgr.Access("role=server;");
EXPECT_EQ(obj, nullptr);
app.Shutdown();
}
// Negative: connect to non-existent server
TEST(WinTunnelNegative, ConnectToNonExistentServer)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
std::string cs = "proto=tunnel;path=" + UniqueUds("no_server") + ";";
auto obj = mgr.Access(cs);
if (!obj) {
SUCCEED() << "Client object is nullptr as expected when server does not exist";
} else {
auto* client = obj->GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(client, nullptr);
EXPECT_FALSE(client->WaitForConnection(200));
}
app.Shutdown();
}
// Edge: double disconnect
TEST(WinTunnelEdge, DoubleDisconnect)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
std::string cs = "proto=tunnel;path=" + UniqueUds("double_disc") + ";";
auto ep = mgr.CreateEndpoint(cs);
auto obj = mgr.Access(ep.ssConnectString);
auto* conn = obj->GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(conn, nullptr);
conn->Disconnect();
// Should not crash or throw
EXPECT_NO_THROW(conn->Disconnect());
app.Shutdown();
}
// Edge: repeated connect/disconnect cycles (recreate endpoint each time)
TEST(WinTunnelEdge, RepeatedConnectDisconnect)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
std::string cs = "proto=tunnel;path=" + UniqueUds("repeat") + ";";
for (int i = 0; i < 3; ++i) {
auto ep = mgr.CreateEndpoint(cs); // recreate endpoint every time
sdv::TObjectPtr obj = mgr.Access(ep.ssConnectString);
auto* conn = obj.GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(conn, nullptr);
conn->AsyncConnect(nullptr);
conn->WaitForConnection(200);
conn->Disconnect();
// obj goes out of scope and cleans up
}
app.Shutdown();
}
// Edge: simultaneous multiple clients
TEST(WinTunnelEdge, MultipleClients)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(""));
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
std::string cs = "proto=tunnel;path=" + UniqueUds("multi_client") + ";";
auto ep = mgr.CreateEndpoint(cs);
auto obj1 = mgr.Access(ep.ssConnectString);
auto obj2 = mgr.Access(ep.ssConnectString);
auto* c1 = obj1->GetInterface<sdv::ipc::IConnect>();
auto* c2 = obj2->GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(c1, nullptr);
ASSERT_NE(c2, nullptr);
c1->AsyncConnect(nullptr);
c2->AsyncConnect(nullptr);
c1->WaitForConnection(500);
c2->WaitForConnection(500);
c1->Disconnect();
c2->Disconnect();
app.Shutdown();
}
// Edge: callback throws exception (should not crash)
class ThrowingReceiver : 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 SetConnectState(sdv::ipc::EConnectState) override { throw std::runtime_error("SetConnectState fail"); }
void ReceiveData(sdv::sequence<sdv::pointer<uint8_t>>&) override { throw std::runtime_error("ReceiveData fail"); }
};
TEST(WinTunnelEdge, CallbackThrows)
{
sdv::app::CAppControl app;
ASSERT_TRUE(app.Startup(R"toml([LogHandler]
ViewFilter = "Fatal")toml"));
CSocketsTunnelChannelMgnt mgr;
mgr.Initialize("");
mgr.SetOperationMode(sdv::EOperationMode::running);
std::string cs = "proto=tunnel;path=" + UniqueUds("cb_throw") + ";";
auto ep = mgr.CreateEndpoint(cs);
auto obj = mgr.Access(ep.ssConnectString);
auto* conn = obj->GetInterface<sdv::ipc::IConnect>();
ASSERT_NE(conn, nullptr);
ThrowingReceiver rcv;
// Should not crash even if callback throws
EXPECT_NO_THROW(conn->AsyncConnect(&rcv));
conn->Disconnect();
app.Shutdown();
}