connection between 2 systems and bug fixes (#14)

This commit is contained in:
tompzf
2026-07-03 14:53:48 +02:00
committed by GitHub
parent 0fb5660d27
commit 46842200f1
284 changed files with 35200 additions and 8265 deletions

View File

@@ -170,6 +170,14 @@ add_custom_command(
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/repository.idl -O${INTERFACE_DIR}
VERBATIM
)
add_custom_command(
OUTPUT ${INTERFACE_DIR}/permission.h
DEPENDS sdv_idl_compiler
MAIN_DEPENDENCY ${INTERFACE_DIR}/permission.idl
COMMENT "Compiling permission.idl"
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/permission.idl -O${INTERFACE_DIR} --no_ps
VERBATIM
)
add_custom_command(
OUTPUT ${INTERFACE_DIR}/timer.h
DEPENDS sdv_idl_compiler
@@ -198,6 +206,7 @@ add_custom_target(CompileCoreIDL
${INTERFACE_DIR}/process.h
${INTERFACE_DIR}/param.h
${INTERFACE_DIR}/repository.h
${INTERFACE_DIR}/permission.h
${INTERFACE_DIR}/timer.h
)
@@ -212,10 +221,14 @@ add_subdirectory(task_timer)
add_subdirectory(ipc_com)
add_subdirectory(ipc_connect)
add_subdirectory(ipc_shared_mem)
add_subdirectory(uds_unix_sockets)
add_subdirectory(uds_unix_tunnel)
add_subdirectory(uds_win_sockets)
add_subdirectory(uds_win_tunnel)
if(UNIX)
add_subdirectory(uds_unix_sockets)
add_subdirectory(uds_unix_tunnel)
endif()
if(WIN32)
add_subdirectory(uds_win_sockets)
add_subdirectory(uds_win_tunnel)
endif()
add_subdirectory(process_control)
add_subdirectory(hardware_ident)
add_subdirectory(manifest_util)

View File

@@ -118,7 +118,7 @@ private:
*/
void PlaybackFunc(const asc::SCanMessage& rsMsg);
std::thread m_threadReceive; ///< Receive thread.
sdv::core::secure_thread m_threadReceive; ///< Receive thread.
mutable std::mutex m_mtxReceivers; ///< Protect the receiver set.
std::set<sdv::can::IReceive*> m_setReceivers; ///< Set with receiver interfaces.
mutable std::mutex m_mtxInterfaces; ///< Protect the nodes set.

View File

@@ -64,7 +64,7 @@ bool CCANSockets::OnInitialize()
}
LogConfigurations();
m_threadReceive = std::thread(&CCANSockets::ReceiveThreadFunc, this);
m_threadReceive = sdv::core::secure_thread(&CCANSockets::ReceiveThreadFunc, this);
return true;
}

View File

@@ -146,7 +146,7 @@ private:
std::string name; ///< interface name, can be empty in case of an invalid socket element
};
std::thread m_threadReceive; ///< Receive thread.
sdv::core::secure_thread m_threadReceive; ///< Receive thread.
mutable std::mutex m_mtxReceivers; ///< Protect the receiver set.
std::set<sdv::can::IReceive*> m_setReceivers; ///< Set with receiver interfaces.
mutable std::mutex m_mtxSockets; ///< Protect the socket list.

View File

@@ -49,13 +49,14 @@ add_library(core_services SHARED
"toml_parser_util.h"
"installation_manifest.h"
"installation_manifest.cpp"
"../../global/tracefifo/trace_fifo.cpp"
"local_shutdown_request.h"
"iso_monitor.h"
"iso_monitor.cpp"
"installation_composer.h"
"installation_composer.cpp"
"toml_parser/lexer_toml_token.h" "toml_parser/lexer_toml_token.cpp" "toml_parser/miscellaneous.h" "toml_parser/miscellaneous.cpp" "toml_parser/code_snippet.h" "toml_parser/code_snippet.cpp" "app_settings.h" "app_settings.cpp" "app_config_file.h" "app_config_file.cpp")
"toml_parser/lexer_toml_token.h" "toml_parser/lexer_toml_token.cpp" "toml_parser/miscellaneous.h" "toml_parser/miscellaneous.cpp" "toml_parser/code_snippet.h" "toml_parser/code_snippet.cpp" "app_settings.h" "app_settings.cpp" "app_config_file.h" "app_config_file.cpp" "toml_parser/parser_node_indexer.h" "toml_parser/parser_node_indexer.cpp" "permission_control.h" "permission_control.cpp")
# Compiler settiings
add_compile_definitions(SDV_NO_EXPORT_DEFINITION)

View File

@@ -33,61 +33,6 @@ CAppConfig& GetAppConfig()
return app_config;
}
// GetCoreDirectory might have been redirected for unit tests.
#ifndef GetCoreDirectory
std::filesystem::path GetCoreDirectory()
{
static std::filesystem::path pathCoreDir;
if (!pathCoreDir.empty())
return pathCoreDir;
#ifdef _WIN32
// Windows specific
std::wstring ssPath(32768, '\0');
MEMORY_BASIC_INFORMATION sMemInfo{};
if (!VirtualQuery(&pathCoreDir, &sMemInfo, sizeof(sMemInfo)))
return pathCoreDir;
DWORD dwLength = GetModuleFileNameW(reinterpret_cast<HINSTANCE>(sMemInfo.AllocationBase), ssPath.data(), 32767);
ssPath.resize(dwLength);
pathCoreDir = std::filesystem::path(ssPath);
return pathCoreDir.remove_filename();
#elif __linux__
// Read the maps file. It contains all loaded SOs.
std::ifstream fstream("/proc/self/maps");
std::stringstream sstreamMap;
sstreamMap << fstream.rdbuf();
std::string ssMap = sstreamMap.str();
if (ssMap.empty())
return pathCoreDir; // Some error
// Find the "core_services.sdv"
size_t nPos = ssMap.find("core_services.sdv");
if (nPos == std::string::npos)
return pathCoreDir;
size_t nEnd = nPos;
// Find the start... runbackwards until the beginning of the line and remember the earliest occurance of a slash
size_t nBegin = 0;
while (nPos && ssMap[nPos] != '\n')
{
if (ssMap[nPos] == '/')
nBegin = nPos;
nPos--;
}
if (!nBegin)
nBegin = nPos;
// Return the path
pathCoreDir = ssMap.substr(nBegin, nEnd - nBegin);
return pathCoreDir;
#else
#error The OS is not supported!
#endif
}
#endif // !defined GetCoreDirectory
bool CAppConfig::LoadInstallationManifests()
{
// Check for allowance
@@ -96,13 +41,14 @@ bool CAppConfig::LoadInstallationManifests()
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::external:
case sdv::app::EAppContext::maintenance:
bServerApp = true;
default:
break;
}
std::filesystem::path pathCore = GetCoreDirectory();
std::filesystem::path pathCore = GetAppSettings().GetFrameworkDir();
std::filesystem::path pathExe = GetExecDirectory();
std::filesystem::path pathInstall = GetAppSettings().GetInstallDir();
@@ -155,8 +101,8 @@ void CAppConfig::UnloadInstallatonManifests()
bool CAppConfig::LoadAppConfigs()
{
// Isolated applications do not load configurations
if (GetAppSettings().IsIsolatedApplication()) return false;
// Isolated and external applications do not load configurations
if (GetAppSettings().IsIsolatedApplication() || GetAppSettings().IsExternalApplication()) return false;
// When running as server application, load the system configurations
if (GetAppSettings().IsMainApplication() || GetAppSettings().IsMaintenanceApplication())
@@ -243,6 +189,7 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::external:
case sdv::app::EAppContext::maintenance:
return sdv::core::EConfigProcessResult::failed;
default:
@@ -275,7 +222,7 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st
//size_t nLoadable = 0;
//size_t nNotLoadable = 0;
//if (!GetAppSettings().IsMainApplication() && !GetAppSettings().IsIsolatedApplication() &&
// !GetAppSettings().IsMaintenanceApplication())
// !GetAppSettings().IsExternalApplication() && !GetAppSettings().IsMaintenanceApplication())
//{
// // Load all modules in the component section
// auto ptrComponents = parser.Root().Direct("Component");
@@ -394,7 +341,7 @@ sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8st
// // loaded; get the module ID.
// sdv::core::TObjectID tObjectID = 0;
// if (!GetAppSettings().IsMainApplication() && !GetAppSettings().IsIsolatedApplication() &&
// !GetAppSettings().IsMaintenanceApplication())
// !GetAppSettings().IsExternalApplication() && !GetAppSettings().IsMaintenanceApplication())
// {
// auto itModule = mapModules.find(pathModule);
// if (itModule == mapModules.end()) continue; // Module was not loaded before...
@@ -880,6 +827,7 @@ std::filesystem::path CAppConfig::FindInstalledModule(const std::filesystem::pat
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::external:
case sdv::app::EAppContext::maintenance:
bServerApp = true;
default:
@@ -914,6 +862,7 @@ std::string CAppConfig::FindInstalledModuleManifest(const std::filesystem::path&
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::external:
case sdv::app::EAppContext::maintenance:
bServerApp = true;
default:
@@ -948,6 +897,7 @@ std::optional<sdv::SClassInfo> CAppConfig::FindInstalledComponent(const std::str
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::external:
case sdv::app::EAppContext::maintenance:
bServerApp = true;
default:
@@ -980,6 +930,7 @@ bool CAppConfig::RemoveFromConfig(const CInstallManifest& /*rManifest*/)
//{
//case sdv::app::EAppContext::main:
//case sdv::app::EAppContext::isolated:
//case sdv::app::EAppContext::external:
//case sdv::app::EAppContext::maintenance:
// bServerApp = true;
//default:
@@ -1043,7 +994,7 @@ void CAppConfig::AddCurrentPath()
if (!m_lstSearchPaths.empty()) return;
// Add the core directory
std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal();
std::filesystem::path pathCoreDir = GetAppSettings().GetFrameworkDir().lexically_normal();
m_lstSearchPaths.push_back(pathCoreDir / "config/");
// Add the exe dir

View File

@@ -39,12 +39,6 @@
// The installation might need companion files to be installed in various relative sub-directories.
// @endcond
/**
* @brief Get the location of the core_services.sdv.
* @return Path to the directory containing the loaded core directory.
*/
std::filesystem::path GetCoreDirectory();
/**
* @brief Application configuration service
* @details In the configuration system objects, devices, basic services, complex services and apps are defined and will be started

View File

@@ -346,9 +346,9 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml(
// Modules leftover in the vector are added to the list
auto vecModuleListCopy = m_vecModuleList;
sdv::toml::CNodeCollection nodeModules = nodeConfig.GetDirect("Module");
for (size_t nIndex = nodeModules.GetCount() - 1; nIndex < nodeModules.GetCount(); --nIndex)
for (int64_t iIndex = static_cast<int64_t>(nodeModules.GetCount() - 1); iIndex >= 0; --iIndex)
{
sdv::toml::CNodeCollection tableModule = nodeModules.Get(nIndex);
sdv::toml::CNodeCollection tableModule = nodeModules.Get(static_cast<size_t>(iIndex));
std::filesystem::path pathModule = tableModule.GetDirect("Path").GetValue().get<std::string>();
auto itModule = std::find_if(vecModuleListCopy.begin(), vecModuleListCopy.end(),
[&](const SModule& rsModule) { return pathModule == rsModule.pathModule; });
@@ -374,9 +374,9 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml(
// Classes leftover in the vector are added to the list
auto vecClassListCopy = m_vecClassList;
sdv::toml::CNodeCollection arrayClasses = nodeConfig.GetDirect("Class");
for (size_t nIndex = arrayClasses.GetCount() -1; nIndex < arrayClasses.GetCount(); --nIndex)
for (int64_t iIndex = static_cast<int64_t>(arrayClasses.GetCount() - 1); iIndex >= 0; --iIndex)
{
sdv::toml::CNodeCollection tableClass = arrayClasses.Get(nIndex);
sdv::toml::CNodeCollection tableClass = arrayClasses.Get(static_cast<size_t>(iIndex));
std::filesystem::path pathModule;
if (!bServerApp)
pathModule = tableClass.GetDirect("Path").GetValue().get<std::string>();
@@ -419,8 +419,7 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml(
if (ssExistingTOML != itClass->ssDefaultConfig)
{
if (tableParams) tableParams.Delete();
if (!itClass->ssDefaultConfig.empty())
tableClass.InsertTOML(sdv::toml::npos, itClass->ssDefaultConfig);
if (!itClass->ssDefaultConfig.empty()) tableClass.InsertTOML("", itClass->ssDefaultConfig);
rbChanged = true;
}
@@ -433,8 +432,7 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml(
tableClass.AddValue("Class", rsClass.ssName);
if (!bServerApp)
tableClass.AddValue("Path", std::filesystem::u8path(static_cast<std::string>(rsClass.ssModulePath)));
if (!rsClass.ssDefaultConfig.empty())
tableClass.InsertTOML(sdv::toml::npos, rsClass.ssDefaultConfig);
if (!rsClass.ssDefaultConfig.empty()) tableClass.InsertTOML("", rsClass.ssDefaultConfig);
rbChanged = true;
}
@@ -444,9 +442,9 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml(
// Components leftover in the vector are added to the list
auto vecComponentListCopy = m_vecComponentList;
sdv::toml::CNodeCollection nodeComponents = nodeConfig.GetDirect("Component");
for (size_t nIndex = nodeComponents.GetCount() - 1; nIndex < nodeComponents.GetCount(); --nIndex)
for (int64_t iIndex = static_cast<int64_t>(nodeComponents.GetCount() - 1); iIndex >= 0; --iIndex)
{
sdv::toml::CNodeCollection tableComponent = nodeComponents.Get(nIndex);
sdv::toml::CNodeCollection tableComponent = nodeComponents.Get(static_cast<size_t>(iIndex));
std::filesystem::path pathModule;
if (!bServerApp)
pathModule = tableComponent.GetDirect("Path").GetValue().get<std::string>();
@@ -518,8 +516,8 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml(
else if (!tableParams)
{
// Simply add the parameters
tableParams = tableComponent.InsertTable(sdv::toml::npos, "Parameters");
tableParams.InsertTOML(sdv::toml::npos, itComponent->ssParameterTOML);
tableParams = tableComponent.InsertTable("", "Parameters");
tableParams.InsertTOML("", itComponent->ssParameterTOML);
rbChanged = true;
}
}
@@ -536,8 +534,8 @@ Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml(
tableComponent.AddValue("Name", rsComponent.ssInstanceName);
if (!rsComponent.ssParameterTOML.empty())
{
sdv::toml::CNodeCollection tableParams = tableComponent.InsertTable(sdv::toml::npos, "Parameters");
tableParams.InsertTOML(sdv::toml::npos, rsComponent.ssParameterTOML);
sdv::toml::CNodeCollection tableParams = tableComponent.InsertTable("", "Parameters");
tableParams.InsertTOML("", rsComponent.ssParameterTOML);
}
rbChanged = true;
}
@@ -628,12 +626,12 @@ bool CAppConfigFile::InsertComponent(size_t nIndex, const std::filesystem::path&
// Need to add a group?
if (prKey.first != ssGroup)
{
group = root.InsertTable(sdv::toml::npos, prKey.first);
group = root.InsertTable("", prKey.first);
ssGroup = prKey.first;
}
// Add the parameter
group.InsertValue(sdv::toml::npos, prKey.second, prParameter.second);
group.InsertValue("", prKey.second, prParameter.second);
}
ssParameterTOML = parser.GenerateTOML();
}

View File

@@ -266,6 +266,9 @@ public:
*/
void RemoveModule(const std::filesystem::path& rpathModule);
/**
* @brief Merge result enum.
*/
enum class EMergeResult
{
successful,

View File

@@ -15,7 +15,6 @@
#include "module_control.h"
#include "repository.h"
#include "../../global/base64.h"
#include "../../global/tracefifo/trace_fifo.cpp"
#include "toml_parser/parser_toml.h"
#include "local_shutdown_request.h"
#include "app_settings.h"
@@ -49,6 +48,9 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
{
m_pEvent = pEventHandler ? pEventHandler->GetInterface<sdv::app::IAppEvent>() : nullptr;
// Allow this thread full access
m_optpermission = GetPermissionControl().CreatePermissionObject(sdv::core::EAccessPermission::full_access);
// Intercept the logging...
std::stringstream sstreamCOUT, sstreamCLOG, sstreamCERR;
std::streambuf* pstreambufCOUT = std::cout.rdbuf(sstreamCOUT.rdbuf());
@@ -86,7 +88,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
}
// Open the stream buffer and attach the streams if the application control is initialized as main app.
if (GetAppSettings().IsMainApplication())
if (!GetAppSettings().RedirectMonitorToConsole() && GetAppSettings().IsMainApplication())
{
m_fifoTraceStreamBuffer.SetInstanceID(GetAppSettings().GetInstanceID());
m_fifoTraceStreamBuffer.Open(1000);
@@ -99,7 +101,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
std::cerr << sstreamCERR.str();
// Check for a correctly opened stream buffer
if (GetAppSettings().IsMainApplication() && !m_fifoTraceStreamBuffer.IsOpened())
if (!GetAppSettings().RedirectMonitorToConsole() && GetAppSettings().IsMainApplication() && !m_fifoTraceStreamBuffer.IsOpened())
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Log streaming could not be initialized; cannot continue!" << std::endl;
@@ -142,7 +144,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
};
auto fnCreateObject = [&ssErrorString](const sdv::u8string& rssClass, const sdv::u8string& rssObject, const sdv::u8string& rssConfig) -> bool
{
bool bLocalRet = GetRepository().CreateObject2(rssClass, rssObject, rssConfig);
bool bLocalRet = GetRepository().CreateObject(rssClass, rssObject, rssConfig);
if (!bLocalRet)
{
ssErrorString = std::string("Failed to instantiate a new object from class '") + rssClass + "'";
@@ -179,7 +181,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
sdv::IInterfaceAccess* pLoggerObj = GetRepository().GetObject(GetAppSettings().GetLoggerClass());
if (!pLoggerObj)
{
GetRepository().DestroyObject2(GetAppSettings().GetLoggerClass());
GetRepository().DestroyObject(GetAppSettings().GetLoggerClass());
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl;
Shutdown(true);
@@ -247,7 +249,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
}
// Load the application settings.
if (!GetAppSettings().LoadSettingsFile())
if (!GetAppSettings().LoadSettings())
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load application settings file. Cannot continue!" << std::endl;
@@ -275,32 +277,88 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
bRet = fnLoadModule("hardware_ident.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("HardwareIdentificationService", "", "");
// Load shared memory channel
if (bRet) bRet = fnLoadModule("ipc_shared_mem.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("DefaultSharedMemoryChannelControl", "", "");
// Load default communication provider
if (bRet) bRet = fnCreateObject(GetAppSettings().GetDefaultComProvider(), "", "");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_com.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("CommunicationControl", "", "");
// Load IPC service and create listener local connections
if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ConnectionListenerService", "", R"code([Listener]
Type = "Local"
)code");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ConnectionService", "", "");
// Load proxy/stub for core interfaces
if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false;
// // Start the listener
// if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false;
// if (bRet) bRet = GetRepository().CreateObject("ConnectionListenerService", "ConnectionListenerService", R"code([Listener]
//Type="local"
//Instance=)code" + std::to_string(GetInstanceID()));
// Load the connection services
if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false;
if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false;
// For the main application start the configured listeners.
if (bRet)
{
auto seqListeners = GetAppSettings().GetListeners();
for (const auto& rssListener : seqListeners)
{
if (!bRet) break;
auto ssListenerConfig = GetAppSettings().GetListenerConfig(rssListener);
bRet = fnCreateObject("ListenerConnectService", "Listener_" + rssListener, ssListenerConfig);
}
}
// For the main application connect the configured connections (except default connection).
if (bRet)
{
auto seqConnections = GetAppSettings().GetConnections();
for (const auto& rssConnection : seqConnections)
{
if (!bRet) break;
if (rssConnection == "Default") continue;
if (!GetAppSettings().IsConsoleSilent())
std::cout << "INFO: Trying to connect to " << rssConnection << "..." << std::endl;
auto ssConnectionConfig = GetAppSettings().GetConnectionConfig(rssConnection);
std::string ssObjectName = "Client_" + rssConnection;
bRet = fnCreateObject("ClientConnectService", ssObjectName, ssConnectionConfig);
// Try connect (for at least nTries) and when successful bind repositories
sdv::TInterfaceAccessPtr ptrClient = GetRepository().GetObject(ssObjectName);
sdv::com::IClientConnect* pClientConnect = ptrClient.GetInterface<sdv::com::IClientConnect>();
if (pClientConnect)
{
for (uint32_t uiCnt = 0; uiCnt < GetAppSettings().GetConnectRetries(); uiCnt++)
{
// Try to connect. If not working, wait for 300 ms
if (pClientConnect->Connect()) break;
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
if (pClientConnect->IsConnected())
{
// Register the core repo as repo access
sdv::core::TLinkID tLinkID = GetRepository().LinkCoreRepository(pClientConnect->GetRemoteRepository());
if (!GetAppSettings().IsConsoleSilent())
std::cout << "INFO: Connection established... " << std::endl;
// Store the connection object.
m_vecConnections.push_back(std::make_pair(ssObjectName, tLinkID));
}
else
{
ssErrorString = "Not connected...";
if (!GetAppSettings().IsConsoleSilent())
std::cout << "ERROR: Failed to connect to " << ssConnectionConfig << std::endl;
bRet = false;
}
}
else
{
ssErrorString = "No client connect interface...";
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Cannot create connection object for " << rssConnection << std::endl;
bRet = false;
}
}
}
if (!bRet)
{
@@ -314,25 +372,69 @@ Type = "Local"
}
else if (bRet && bLoadRPCClient)
{
// Interpret the connect string...
// Load hardware identification
bRet = fnLoadModule("hardware_ident.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("HardwareIdentificationService", "", "");
// Load shared memory channel
if (bRet) bRet = fnLoadModule("ipc_shared_mem.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("DefaultSharedMemoryChannelControl", "", "");
// Load default communication provider
if (bRet) bRet = fnCreateObject(GetAppSettings().GetDefaultComProvider(), "", "");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_com.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("CommunicationControl", "", "");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ConnectionService", "", "");
// Connect to the core system when running as external aplication. Use the default connection for this.
if (bRet && GetAppSettings().GetContextType() == sdv::app::EAppContext::external)
{
// Load the connection services
bRet = fnLoadModule("ipc_connect.sdv") ? true : false;
if (!GetAppSettings().IsConsoleSilent())
std::cout << "INFO: Trying to connect to the core system..." << std::endl;
// And the one connection
auto ssConnectionConfig = GetAppSettings().GetConnectionConfig("Default");
if (bRet && !ssConnectionConfig.empty())
bRet = fnCreateObject("ClientConnectService", "Client_Default", ssConnectionConfig);
// Try connect (for at least nTries) and when successful bind repositories
sdv::TInterfaceAccessPtr ptrClient = GetRepository().GetObject("Client_Default");
sdv::com::IClientConnect* pClientConnect = ptrClient.GetInterface<sdv::com::IClientConnect>();
if (pClientConnect)
{
for (uint32_t uiCnt = 0; uiCnt < GetAppSettings().GetConnectRetries(); uiCnt++)
{
// Try to connect. If not working, wait for 300 ms
if (pClientConnect->Connect()) break;
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
if (pClientConnect->IsConnected())
{
// Register the core repo as repo access
sdv::core::TLinkID tLinkID = GetRepository().LinkCoreRepository(pClientConnect->GetRemoteRepository());
if (!GetAppSettings().IsConsoleSilent())
std::cout << "INFO: Connection established... " << std::endl;
// Store the connection object.
m_vecConnections.push_back(std::make_pair("Default", tLinkID));
}
else
{
if (!GetAppSettings().IsConsoleSilent())
std::cout << "ERROR: Failed to connect to the core system" << std::endl;
bRet = false;
}
}
else
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Cannot create connection object for connecting to the core system." << std::endl;
bRet = false;
}
}
// Load proxy/stub for core interfaces
if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false;
@@ -428,6 +530,7 @@ void CAppControl::RunLoop()
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::external:
bLocal = false;
break;
case sdv::app::EAppContext::maintenance:
@@ -490,8 +593,22 @@ void CAppControl::Shutdown(/*in*/ bool bForce)
// Disable automatic configuration saving.
m_bAutoSaveConfig = false;
// Update the application settings file
GetAppSettings().SaveSettingsFile();
// TODO EVE: This is likely not wanted
//// Update the application settings file
//GetAppSettings().SaveSettings();
// Disconnect all automatic connections.
for (const auto& rprConnectObject : m_vecConnections)
{
// If there is a registered link ID, unlink the ID.
if (rprConnectObject.second)
GetRepository().UnlinkCoreRepository(rprConnectObject.second);
sdv::TInterfaceAccessPtr ptrClient = GetRepository().GetObject(rprConnectObject.first);
sdv::com::IClientConnect* pClientConnect = ptrClient.GetInterface<sdv::com::IClientConnect>();
if (pClientConnect) pClientConnect->Disconnect();
}
m_vecConnections.clear();
// Destroy all objects... this should also remove any registered services, except the custom logger.
GetRepository().DestroyAllObjects(std::vector<std::string>({GetAppSettings().GetLoggerClass()}), bForce);
@@ -537,7 +654,7 @@ void CAppControl::Shutdown(/*in*/ bool bForce)
}
// End trace streaming
if (GetAppSettings().IsMainApplication())
if (!GetAppSettings().RedirectMonitorToConsole() && GetAppSettings().IsMainApplication())
{
std::cout << "**********************************************" << std::endl;
@@ -551,6 +668,7 @@ void CAppControl::Shutdown(/*in*/ bool bForce)
m_bAutoSaveConfig = false;
m_bEnableAutoSave = false;
GetAppSettings().Reset();
m_optpermission.reset();
}
void CAppControl::RequestShutdown()

View File

@@ -18,6 +18,8 @@
#include <support/component_impl.h>
#include <support/interface_ptr.h>
#include "../../global/tracefifo/trace_fifo.h"
#include "permission_control.h"
#include <optional>
/**
* @brief Application control class.
@@ -140,17 +142,20 @@ private:
*/
void BroadcastOperationState(sdv::app::EAppOperationState eState);
sdv::app::EAppOperationState m_eState = sdv::app::EAppOperationState::not_started; ///< The current operation state.
sdv::app::IAppEvent* m_pEvent = nullptr; ///< Pointer to the app event interface.
sdv::core::TModuleID m_tLoggerModuleID = 0; ///< ID of the logger module.
bool m_bEnableAutoSave = false; ///< When set and when enabled in the system settings, allows
///< the automatic saving of the configuration.
bool m_bRunLoop = false; ///< Used to detect end of running loop function.
std::filesystem::path m_pathLockFile; ///< Lock file path name.
FILE* m_pLockFile = nullptr; ///< Lock file to test for other instances.
CTraceFifoStdBuffer m_fifoTraceStreamBuffer; ///< Trace stream buffer to redirect std::log, std::out and
///< std::err when running as service.
bool m_bAutoSaveConfig = false; ///< System setting for automatic saving of the user configuration.
sdv::app::EAppOperationState m_eState = sdv::app::EAppOperationState::not_started; ///< The current operation state.
sdv::app::IAppEvent* m_pEvent = nullptr; ///< Pointer to the app event interface.
sdv::core::TModuleID m_tLoggerModuleID = 0; ///< ID of the logger module.
bool m_bEnableAutoSave = false; ///< When set and when enabled in the system settings, allows
///< the automatic saving of the configuration.
bool m_bRunLoop = false; ///< Used to detect end of running loop function.
std::filesystem::path m_pathLockFile; ///< Lock file path name.
FILE* m_pLockFile = nullptr; ///< Lock file to test for other instances.
CTraceFifoStdBuffer m_fifoTraceStreamBuffer; ///< Trace stream buffer to redirect std::log, std::out and
///< std::err when running as service.
bool m_bAutoSaveConfig = false; ///< System setting for automatic saving of the user configuration.
std::vector<std::pair<std::string, sdv::core::TLinkID>> m_vecConnections; ///< Connection objects that were successfully
///< connected.
std::optional<CAccessPermission> m_optpermission; ///< Main access permission for the core.
};
/**

File diff suppressed because it is too large Load Diff

View File

@@ -62,6 +62,10 @@
* #Console output
* [Console]
* Report = "Silent" # Either "Silent", "Normal" or "Verbose" for no, normal or extensive messages.
* RedirectMon = true # When set, redirects messages to the console instead of the monitor application in main mode.
*
* [Connections]
* Retries = 5 # The amount of retries when trying to connect (value between 3 and 30, default is 5).
*
* # Search directories
* @endcode
@@ -103,11 +107,40 @@
* # Example
* # AppConfig = "app_config.toml"
* AppConfig = ""
*
* # A list of zero or more listener definitions that should be instantiated during startup of the
* # main application. If no listener definition is available, the default shared-memory listener
* # is being instantiated.
* #
* # [[Settings.Listener]]
* # Name = ""
* # [Settings.Listerner.Provider]
* # Name = ""
* # [Settings.Listener.IpcChannel]
*
* # A list of zero or more client connections that should be instantiated during startup.
* #
* # [[Settings.Connection]]
* # Name = ""
* # [Settings.Connection.Provider]
* # Name = ""
* # [Settings.Listener.IpcChannel]
* @endcode
*/
class CAppSettings : public sdv::IInterfaceAccess, public sdv::app::IAppContext, public sdv::IAttributes
class CAppSettings : public sdv::CSdvParamMap, public sdv::IInterfaceAccess, public sdv::app::IAppContext,
public sdv::app::IAppSettingsPersist, public sdv::app::IAppConnections
{
public:
/**
* @brief Console reporting.
*/
enum class EAppConsoleReporting
{
silent, ///< No reporting by application control (default)
normal, ///< Normal reporting by application control
verbose, ///< Extensive reporting by application control
};
/**
* @brief Constructor
*/
@@ -118,11 +151,71 @@ public:
*/
~CAppSettings();
#ifndef DOXYGEN_IGNORE
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::app::IAppContext)
END_SDV_INTERFACE_MAP()
// Application mode labels
BEGIN_SDV_LABEL_MAP(sdv::app::EAppContext)
SDV_LABEL_ENTRY(sdv::app::EAppContext::no_context, "Undefined")
SDV_LABEL_ENTRY(sdv::app::EAppContext::standalone, "Standalone")
SDV_LABEL_ENTRY(sdv::app::EAppContext::external, "External")
SDV_LABEL_ENTRY(sdv::app::EAppContext::isolated, "Isolated")
SDV_LABEL_ENTRY(sdv::app::EAppContext::main, "Main")
SDV_LABEL_ENTRY(sdv::app::EAppContext::essential, "Essential")
SDV_LABEL_ENTRY(sdv::app::EAppContext::maintenance, "Maintenance")
END_SDV_LABEL_MAP()
// Log severity labels
BEGIN_SDV_LABEL_MAP(sdv::core::ELogSeverity)
SDV_LABEL_ENTRY(sdv::core::ELogSeverity::trace, "Trace")
SDV_LABEL_ENTRY(sdv::core::ELogSeverity::debug, "Debug")
SDV_LABEL_ENTRY(sdv::core::ELogSeverity::info, "Info")
SDV_LABEL_ENTRY(sdv::core::ELogSeverity::warning, "Warning")
SDV_LABEL_ENTRY(sdv::core::ELogSeverity::error, "Error")
SDV_LABEL_ENTRY(sdv::core::ELogSeverity::fatal, "Fatal")
END_SDV_LABEL_MAP()
// Console reporting labels
BEGIN_SDV_LABEL_MAP(EAppConsoleReporting)
SDV_LABEL_ENTRY(EAppConsoleReporting::normal, "Normal")
SDV_LABEL_ENTRY(EAppConsoleReporting::silent, "Silent")
SDV_LABEL_ENTRY(EAppConsoleReporting::verbose, "Verbose")
END_SDV_LABEL_MAP()
// Parameter map
BEGIN_SDV_PARAM_MAP()
SDV_PARAM_SET_READONLY()
SDV_PARAM_GROUP("LogHandler")
SDV_PARAM_ENTRY(m_ssLoggerClass, "Class", "", "", "Component class name of a custom logger")
SDV_PARAM_ENTRY(m_pathLoggerModule, "Path", "", "", "Component module path of a custom logger")
SDV_PARAM_ENTRY(m_ssProgramTag, "Tag", "", "", "Program tag to use instead of the name SDV_LOG_<pid>")
SDV_PARAM_ENUM_ENTRY(m_eSeverityFilter, "Filter", sdv::core::ELogSeverity::info,
"Lowest severity filter to use when logging (Trace, Debug, Info, Warning, Error, Fatal)")
SDV_PARAM_ENUM_ENTRY(m_eSeverityViewFilter, "ViewFilter", sdv::core::ELogSeverity::error,
"Lowest severity filter to use when logging (Trace, Debug, Info, Warning, Error, Fatal)")
SDV_PARAM_GROUP("Application")
SDV_PARAM_ENUM_ENTRY(m_eAppContextType, "Mode", sdv::app::EAppContext::no_context, "Application mode")
SDV_PARAM_ENTRY(m_uiInstanceID, "Instance", 0u, "", "System instance ID")
SDV_PARAM_ENTRY(m_pathRootDir, "RootDir", "", "", "Location of user component root directory")
SDV_PARAM_ENTRY(m_pathInstallDir, "InstallDir", "", "", "Location of user component installations")
SDV_PARAM_ENTRY(m_pathPlatformConfig, "PlatformConfig", "", "", "The platform abstraction configuration file")
SDV_PARAM_ENTRY(m_pathVehIfcConfig, "VehIfcConfig", "", "", "The vehicle interface configuration file")
SDV_PARAM_ENTRY(m_pathVehAbstrConfig, "VehAbstrConfig", "", "", "The vehicle abstraction configuration file")
SDV_PARAM_ENTRY(m_pathUserConfig, "AppConfig", "", "", "The application configuration file")
SDV_PARAM_GROUP("Console")
SDV_PARAM_ENUM_ENTRY(m_eConsoleReporting, "Reporting", EAppConsoleReporting::normal, "Console reporting (Normal, Silent, Verbose)")
SDV_PARAM_ENTRY(m_bRedirectMon, "RedirectMon", false, "", "Redirect messages from the monitor onto the console.")
SDV_PARAM_GROUP("Communication")
SDV_PARAM_ENTRY(m_ssDefaultComProvider, "DefaultProvider", "", "", "Name of the default communication provider")
END_SDV_PARAM_MAP()
#endif // !defined DOXYGEN_IGNORE
/**
* @brief Process the application starrtup configuration.
* @param[in] rssConfig Reference to the configuration content (TOML format).
@@ -131,19 +224,20 @@ public:
bool ProcessAppStartupConfig(const sdv::u8string& rssConfig);
/**
* @brief Load the application settings file.
* @brief Load the application settings file. Overload of sdv::app::IAppSettingsPersist::LoadSettings.
* @attention Only works if the application is running in main, isolation or maintenance mode.
* @remarks When there is no settings file, this is not an error. Default settings will be assumed.
* @return Returns whether the loading was successful.
*/
bool LoadSettingsFile();
bool LoadSettings() override;
/**
* @brief Save the application settings file (or create when not existing yet).
* @attention Only works if the application is running in main, isolation or maintenance mode.
* Overload of sdv::app::IAppSettingsPersist::SaveSettings.
* @attention Only works if the application is running in maintenance mode.
* @return Returns whether the saving was successful.
*/
bool SaveSettingsFile();
bool SaveSettings() override;
/**
* @brief Return whether the current application is the main application.
@@ -181,27 +275,33 @@ public:
*/
bool IsExternalApplication() const;
/**
* @brief Set the application context type.
* @param[in] eContextType The application context type to set.
*/
void SetContextType(sdv::app::EAppContext eContextType);
/**
* @brief Return the application context mode. Overload of sdv::app::IAppContext::GetContextType.
* @return The context mode.
*/
sdv::app::EAppContext GetContextType() const override;
/**
* @brief Set the instance ID.
* @param uiID The instance ID to set.
*/
void SetInstanceID(uint32_t uiID);
/**
* @brief Return the core instance ID. Overload of sdv::app::IAppContext::GetContextType.
* @details Get the instance. If not otherwise specified, the current instance depends on whether the application is running
* as main or isolated application, in which case the instance is 1000. In all other cases the instance is 0. An instance
* ID can be supplied through the app startup configuration.
* as main, isolated or maintenance application, in which case the instance is 1000. In all other cases the instance is 0. An
* instance ID can be supplied through the app startup configuration.
* @return The core instance ID.
*/
uint32_t GetInstanceID() const override;
/**
* @brief Return the number of retries to establish a connection. Overload of sdv::app::IAppContext::GetRetries.
* @return Number of retries.
*/
uint32_t GetRetries() const override;
/**
* @brief Get the class name of a logger service, if specified in the application startup configuration.
* @return The logger class name.
@@ -244,6 +344,30 @@ public:
*/
bool IsConsoleVerbose() const;
/**
* @brief Set the application console reporting status.
* @param eReporting The console reporting status to set.
*/
void SetConsoleReporting(EAppConsoleReporting eReporting);
/**
* @brief Get the current application console reporting status.
* @return The console reporting status of the application.
*/
EAppConsoleReporting GetConsoleReporting() const;
/**
* @brief Redirect the messages monitored for main application onto the console.
* @return Returns whether redirection is switched on.
*/
bool RedirectMonitorToConsole() const;
/**
* @brief Get the framework directory for the application.
* @return The location of framework directory.
*/
std::filesystem::path GetFrameworkDir() const;
/**
* @brief Get the root directory for the application.
* @remarks Is only valid when used in main, isolated and maintenance applications.
@@ -279,6 +403,7 @@ public:
/**
* @brief Get the stored or default configuration path name.
* @attention Setting a path is only valid when running as main application.
* @param[in] eType The configuration type to get the path for.
* @return The path name dependent on the configuration type. If no path name was configured, the default path name is returned.
*/
std::filesystem::path GetConfigPath(EConfigType eType) const;
@@ -326,33 +451,80 @@ public:
bool RemoveUserConfigPath();
/**
* @brief Get a sequence with the available attribute names. Overload of sdv::IAttributes::GetNames.
* @return The sequence of attribute names.
* @brief Get the default communication provider (extracted from listener and connection settings).
* @return Name of the default communication provider.
*/
virtual sdv::sequence<sdv::u8string> GetNames() const override;
const std::string& GetDefaultComProvider() const;
/**
* @brief Get the attribute value. Overload of sdv::IAttributes::Get.
* @param[in] ssAttribute Name of the attribute.
* @return The attribute value or an empty any-value if the attribute wasn't found or didn't have a value.
* @brief Get a sequence with listener names. Overload of sdv::app::IAppConnections::GetListeners.
* @return Sequence with listener name strings.
*/
virtual sdv::any_t Get(/*in*/ const sdv::u8string& ssAttribute) const override;
sdv::sequence<sdv::u8string> GetListeners() const override;
/**
* @brief Set the attribute value. Overload of sdv::IAttributes::Set.
* @param[in] ssAttribute Name of the attribute.
* @param[in] anyAttribute Attribute value to set.
* @return Returns 'true' when setting the attribute was successful or 'false' when the attribute was not found or the
* attribute is read-only or another error occurred.
* @brief Get the listener configuration. Overload of sdv::app::IAppConnections::GetListenerConfig.
* @param[in] ssName Name of the listener.
* @return String containing the listener configuration.
*/
virtual bool Set(/*in*/ const sdv::u8string& ssAttribute, /*in*/ sdv::any_t anyAttribute) override;
sdv::u8string GetListenerConfig(/*in*/ const sdv::u8string& ssName) const override;
/**
* @brief Get the attribute flags belonging to a certain attribute. Overload of sdv::IAttributes::GetFlags.
* @param[in] ssAttribute Name of the attribute.
* @return Returns the attribute flags (zero or more EAttributeFlags flags) or 0 when the attribute could not be found.
* @brief Add or update a listener configuration. Overload of sdv::app::IAppConnections::AddListenerConfig.
* @remarks Only accessible when the application runs in maintenance mode.
* @param[in] ssName Name of the listener configuration.
* @param[in] ssConfig The configuration string for the listener.
* @return Returns whether the listener could be added (fails when the listener already exists).
*/
virtual uint32_t GetFlags(/*in*/ const sdv::u8string& ssAttribute) const override;
bool AddListenerConfig(/*in*/ const sdv::u8string& ssName, /*in*/ const sdv::u8string& ssConfig) override;
/**
* @brief Remove a listener configuration with the provided name. Overload of sdv::app::IAppConnections::RemoveListenerConfig.
* @remarks Only accessible when the application runs in maintenance mode.
* @param[in] ssName Name of the listener configuration.
* @return Returns whether the removal was successful.
*/
bool RemoveListenerConfig(/*in*/ const sdv::u8string& ssName) override;
/**
* @brief Get a sequence with connection names. Overload of sdv::app::IAppConnections::GetConnections.
* @return Sequence with connection name strings.
*/
sdv::sequence<sdv::u8string> GetConnections() const override;
/**
* @brief Get the connection configuration. Overload of sdv::app::IAppConnections::GetConnectionConfig.
* @param[in] ssName Name of the connection.
* @return String containing the connection configuration.
*/
sdv::u8string GetConnectionConfig(/*in*/ const sdv::u8string& ssName) const override;
/**
* @brief Add or update a connection configuration. Overload of sdv::app::IAppConnections::AddConnectionConfig.
* @remarks Only accessible when the application runs in maintenance mode.
* @param[in] ssName Name of the connection configuration.
* @param[in] ssConfig The configuration string for the connection.
* @param[in] ssInsertBefore Reference to the string to connection to insert the the new connection before, or empty when the
* the new connection should be placed at the end.
* @return Returns whether the connection could be added (fails when the connection already exists).
*/
bool AddConnectionConfig(/*in*/ const sdv::u8string& ssName, /*in*/ const sdv::u8string& ssConfig,
/*in*/ const sdv::u8string& ssInsertBefore = std::string()) override;
/**
* @brief Remove a connection configuration with the provided name. Overload of
* sdv::app::IAppConnections::RemoveConnectionConfig.
* @remarks Only accessible when the application runs in maintenance mode.
* @param[in] ssName Name of the connection configuration.
* @return Returns whether the removal was successful.
*/
bool RemoveConnectionConfig(/*in*/ const sdv::u8string& ssName) override;
/**
* @brief Return the number of retries to establish a connection. Overload of sdv::app::IAppConnections::GetRetries.
* @return Number of retries.
*/
uint32_t GetConnectRetries() const override;
/**
* @brief Reset the settings after a shutdown.
@@ -360,26 +532,42 @@ public:
void Reset();
private:
sdv::app::EAppContext m_eContextMode = sdv::app::EAppContext::no_context; ///< The application is running as...
uint32_t m_uiInstanceID = 0u; ///< Instance number.
uint32_t m_uiRetries = 0u; ///< Number of retries to establish a connection.
/**
* @brief Get the default listener config.
* @return String containing the TOML with the listener config.
*/
std::string DefaultListenerConfig() const;
/**
* @brief Get the default connection config.
* @return String containing the TOML with the connection config.
*/
std::string DefaultConnectionConfig() const;
sdv::app::EAppContext m_eAppContextType = sdv::app::EAppContext::no_context; ///< The application is running as...
uint32_t m_uiInstanceID = 0u; ///< Instance number (default 1000, but only after startup).
uint32_t m_uiConnectRetries = 5u; ///< Number of retries to establish a connection.
std::string m_ssLoggerClass; ///< Class name of a logger service.
std::filesystem::path m_pathLoggerModule; ///< Module name of a custom logger.
std::string m_ssProgramTag; ///< Program tag to use when logging.
sdv::core::ELogSeverity m_eSeverityFilter = sdv::core::ELogSeverity::info; ///< Severity level filter while logging.
sdv::core::ELogSeverity m_eSeverityViewFilter = sdv::core::ELogSeverity::error; ///< Severity level filter while logging.
bool m_bSilent = false; ///< When set, no console reporting takes place.
bool m_bVerbose = false; ///< When set, extensive console reporting takes place.
EAppConsoleReporting m_eConsoleReporting = EAppConsoleReporting::normal; ///< Console reporting
bool m_bRedirectMon = false; ///< When set, redirect the message from the monitor onto console.
std::filesystem::path m_pathFrameworkDir; ///< Location of framework component directory.
std::filesystem::path m_pathRootDir; ///< Location of user component root directory.
std::filesystem::path m_pathInstallDir; ///< Location of user component installations (root with instance).
std::filesystem::path m_pathPlatformConfig; ///< The platform configuration from the settings file.
std::filesystem::path m_pathVehIfcConfig; ///< The vehicle interface configuration from the settings file.
std::filesystem::path m_pathVehAbstrConfig; ///< The vehicle abstraction configuration from the settings file.
std::filesystem::path m_pathUserConfig; ///< The user configuration from the settings file.
bool m_bPlatformConfig = false; ///< Platform config was explicitly enabled/disabled.
bool m_bVehIfcConfig = false; ///< Vehicle interface config was explicitly enabled/disabled.
bool m_bVehAbstrConfig = false; ///< Vehicle abstraction config was explicitly enabled/disabled.
bool m_bUserConfig = false; ///< User config was explicitly enabled/disabled.
bool m_bUpdatePlatformConfig = false; ///< Platform config was explicitly marked for update.
bool m_bUpdateVehIfcConfig = false; ///< Vehicle interface config was explicitly marked for update.
bool m_bUpdateVehAbstrConfig = false; ///< Vehicle abstraction config was explicitly marked for update.
bool m_bUpdateUserConfig = false; ///< User config was explicitly marked for update.
std::map<std::string, std::string> m_mapListeners; ///< Map with listener configurations.
std::vector<std::pair<std::string, std::string>> m_vecConnections; ///< Vector with connection configurations.
std::string m_ssDefaultComProvider; ///< Name of the default communication provider.
};
/**
@@ -398,7 +586,11 @@ public:
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY_MEMBER(sdv::IAttributes, GetAppSettings())
SDV_INTERFACE_SET_SECTION_CONDITION(GetAppSettings().IsMaintenanceApplication(), 1)
SDV_INTERFACE_SECTION(1)
SDV_INTERFACE_ENTRY_MEMBER(sdv::app::IAppSettingsPersist, GetAppSettings())
SDV_INTERFACE_ENTRY_MEMBER(sdv::app::IAppConnections, GetAppSettings())
SDV_INTERFACE_DEFAULT_SECTION()
END_SDV_INTERFACE_MAP()
// Object declarations
@@ -406,6 +598,11 @@ public:
DECLARE_OBJECT_CLASS_NAME("AppSettingsService")
DECLARE_OBJECT_SINGLETON()
// Parameter map
BEGIN_SDV_PARAM_MAP()
SDV_PARAM_CHAIN_MEMBER(GetAppSettings())
END_SDV_PARAM_MAP()
/**
* @brief Get access to the application settings.
* @return Returns the one global instance of the application config.

View File

@@ -38,6 +38,12 @@
#error OS is not supported!
#endif
namespace
{
constexpr float kMaxBLOBSize = (24*1024*1024) + 25000000;
}
CInstallComposer::~CInstallComposer()
{}
@@ -1019,10 +1025,16 @@ uint32_t CInstallComposer::SerializeModuleBLOB(uint32_t uiChecksumInit, sdv::poi
// Calculate the size of the BLOB (including padding and checksum)
sdv::installation::SPackageBLOBChecksum sBLOBChecksum{};
size_t nSize = 0;
sdv::ser_size(sBLOB, nSize);
sdv::ser_size(sBLOB, nSize);
sdv::ser_size(sBLOBChecksum, nSize);
if (nSize % 8) nSize += 8 - nSize % 8;
sBLOB.uiBLOBSize = static_cast<uint32_t>(nSize);
if(sBLOB.uiBLOBSize > kMaxBLOBSize)
{
sdv::installation::XPackageSizeExceeded exception;
exception.ssFileName = sBLOB.sFileDesc.ssFileName;
throw exception;
}
// Serialize the BLOB
sdv::serializer<sdv::GetPlatformEndianess(), sdv::crcCRC32C> serializer;
@@ -1246,7 +1258,7 @@ sdv::installation::SPackageBLOB CInstallComposer::DeserializeBLOB(std::ifstream&
// Extend the buffer and read the BLOB data (after the resize, the psPartialBLOB pointer could be invalidated).
uint32_t uiBLOBSize = psPartialBLOB->uiBLOBSize;
if (uiBLOBSize > 26*1024*1024) // 26 instead of 24 becasue of door example
if (uiBLOBSize > kMaxBLOBSize)
throw sdv::installation::XIncompatiblePackage(); // Safety
ptrBLOB.resize(uiBLOBSize);
if (!ptrBLOB)

View File

@@ -23,11 +23,11 @@ CIsoMonitor::~CIsoMonitor()
GetAppControl().RequestShutdown();
}
void CIsoMonitor::Initialize(/*in*/ const sdv::u8string& ssObjectConfig)
void CIsoMonitor::Initialize(/*in*/ const sdv::SObjectInfo& sObjectInfo)
{
if (m_pObjectControl)
{
m_pObjectControl->Initialize(ssObjectConfig);
m_pObjectControl->Initialize(sObjectInfo);
m_eObjectState = m_pObjectControl->GetObjectState();
}
else

View File

@@ -42,9 +42,9 @@ public:
/**
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
* @param[in] ssObjectConfig Optional configuration string.
* @param[in] sObjectInfo The registration information of this object.
*/
virtual void Initialize(/*in*/ const sdv::u8string& ssObjectConfig) override;
virtual void Initialize(/*in*/ const sdv::SObjectInfo& sObjectInfo) override;
/**
* @brief Get the current state of the object. Overload of sdv::IObjectControl::GetObjectState.

View File

@@ -61,6 +61,8 @@ inline bool RequestShutdown(uint32_t uiInstanceID = 1000u)
CloseHandle(hEvent);
return true;
}
else
std::cerr << "ERROR: Shutdown event handle is not available: " << m_ssSignalName << std::endl;
#elif defined __unix__
sem_t* pSemaphore = sem_open(m_ssSignalName.c_str(), 0);
if (pSemaphore && pSemaphore != SEM_FAILED)

View File

@@ -14,6 +14,7 @@
#include "logger.h"
#include <sstream>
#include "../../global/exec_dir_helper.h"
#include "app_settings.h"
#ifdef __unix__
#include <syslog.h>
@@ -38,7 +39,7 @@ CLogger::~CLogger()
void CLogger::Log(sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
/*in*/ sdv::process::TProcessID tProcessID, /*in*/ const sdv:: u8string& ssObjectName, /*in*/ const sdv::u8string& ssMessage)
{
if (static_cast<uint32_t>(eSeverity) >= static_cast<uint32_t>(m_eViewFilter))
if (GetAppSettings().IsConsoleVerbose() && static_cast<uint32_t>(eSeverity) >= static_cast<uint32_t>(m_eViewFilter))
{
if (tProcessID) std::clog << "[PID#" << static_cast<int64_t>(tProcessID) << "] ";
if (!ssObjectName.empty()) std::clog << ssObjectName << " ";

View File

@@ -297,6 +297,10 @@ bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept
}
m_mapClassInfo[sClass.ssName] = sClass;
// Add aliases?
for (const auto& rssAlias : sClass.seqClassAliases)
m_mapClassInfo[rssAlias] = sClass;
}
}
catch (const sdv::toml::XTOMLParseException&)

View File

@@ -250,7 +250,7 @@ std::shared_ptr<CModuleInst> CModuleControl::FindModuleByClass(const std::string
}
}
// For main and isolated applications, check whether the module is in one of the installation manifests.
// For main, isolated and maintenance applications, check whether the module is in one of the installation manifests.
auto optManifest = GetAppConfig().FindInstalledComponent(rssClass);
if (!optManifest) return nullptr;
std::filesystem::path pathModule = std::filesystem::u8path(static_cast<std::string>(optManifest->ssModulePath));
@@ -378,8 +378,6 @@ std::string CModuleControl::SaveConfig(const std::set<std::filesystem::path>& /*
sdv::core::TModuleID CModuleControl::ContextLoad(const std::filesystem::path& rpathModule, const std::string& rssManifest)
{
if (GetAppSettings().IsMaintenanceApplication()) return 0; // Not allowed
// Run through the manifest and check for complex services, applications and utilities.
// TODO EVE: Temporary suppression of cppcheck warning.
// cppcheck-suppress variableScope
@@ -462,7 +460,7 @@ void CModuleControl::AddCurrentPath()
if (!m_lstSearchPaths.empty()) return;
// Add the core directory
std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal();
std::filesystem::path pathCoreDir = GetAppSettings().GetFrameworkDir().lexically_normal();
m_lstSearchPaths.push_back(pathCoreDir);
// Add the exe dir

View File

@@ -0,0 +1,167 @@
/********************************************************************************
* 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:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "permission_control.h"
#if defined __GNUC__ && defined _WIN32
thread_local std::list<std::pair<sdv::core::EAccessPermission, size_t>>&
CPermissionControl::m_lstPermissionTracker = CreatePermissionList();
thread_local std::map<sdv::core::TPermissionID, CAccessPermission>&
CPermissionControl::m_mapPermissions = CreatePermissionMap();
#else
thread_local std::list<std::pair<sdv::core::EAccessPermission, size_t>> CPermissionControl::m_lstPermissionTracker;
thread_local std::map<sdv::core::TPermissionID, CAccessPermission> CPermissionControl::m_mapPermissions;
#endif
CPermissionControl& GetPermissionControl()
{
static CPermissionControl control;
return control;
}
CAccessPermission::CAccessPermission(CPermissionControl& rPermissionControl, sdv::core::EAccessPermission ePermission) :
m_rPermissionControl(rPermissionControl), m_ePermission(ePermission)
{
if (m_ePermission != sdv::core::EAccessPermission::not_set)
rPermissionControl.IncrementPermissionCount(ePermission);
}
CAccessPermission::CAccessPermission(const CAccessPermission& rPermission) :
m_rPermissionControl(rPermission.m_rPermissionControl), m_ePermission(rPermission.m_ePermission)
{
if (m_ePermission != sdv::core::EAccessPermission::not_set)
m_rPermissionControl.IncrementPermissionCount(m_ePermission);
}
CAccessPermission::CAccessPermission(CAccessPermission&& rPermission) :
m_rPermissionControl(rPermission.m_rPermissionControl), m_ePermission(rPermission.m_ePermission)
{
rPermission.m_ePermission = sdv::core::EAccessPermission::not_set;
}
CAccessPermission& CAccessPermission::operator=(const CAccessPermission& rPermission)
{
// Remove current permission and copy the new permission
if (m_ePermission != sdv::core::EAccessPermission::not_set)
m_rPermissionControl.DecrementPermissionCount(m_ePermission);
m_ePermission = rPermission.m_ePermission;
if (m_ePermission != sdv::core::EAccessPermission::not_set)
m_rPermissionControl.IncrementPermissionCount(m_ePermission);
return *this;
}
CAccessPermission& CAccessPermission::operator=(CAccessPermission && rPermission)
{
// Remove current permission and move the new permission
if (m_ePermission != sdv::core::EAccessPermission::not_set)
m_rPermissionControl.DecrementPermissionCount(m_ePermission);
m_ePermission = rPermission.m_ePermission;
rPermission.m_ePermission = sdv::core::EAccessPermission::not_set;
return *this;
}
CAccessPermission ::~CAccessPermission()
{
if (m_ePermission != sdv::core::EAccessPermission::not_set)
m_rPermissionControl.DecrementPermissionCount(m_ePermission);
}
sdv::core::EAccessPermission CAccessPermission::Permission() const
{
return m_ePermission;
}
sdv::core::TPermissionID CPermissionControl::RestrictAccessPermission(/*in*/ sdv::core::EAccessPermission ePermission)
{
// Unlike the CreateAccessPermission this function can only be used to restrict an already available restriction. It cannot be
// used to set the initial restriction.
if (m_lstPermissionTracker.empty()) return false;
sdv::core::TPermissionID tPermissionID = m_idgen.Generate();
if (!tPermissionID) return 0u;
m_mapPermissions.emplace(tPermissionID, CreatePermissionObject(ePermission));
return tPermissionID;
}
bool CPermissionControl::ReleaseAccessPermission(/*in*/ sdv::core::TPermissionID tPermissionID)
{
auto itPermission = m_mapPermissions.find(tPermissionID);
if (itPermission == m_mapPermissions.end()) return false;
m_mapPermissions.erase(itPermission);
return true;
}
sdv::core::TPermissionTransferID CPermissionControl::TransferCurrentPermission()
{
std::unique_lock<std::mutex> lock(m_mtxPermissionTransfer);
sdv::core::TPermissionTransferID tTransferID = m_idgen.Generate();
m_mapPermissionTransfer[tTransferID] = GetCurrentPermission();
return tTransferID;
}
sdv::core::TPermissionID CPermissionControl::SetAccessPermission(/*in*/ sdv::core::TPermissionTransferID tTransferID)
{
// Get the access restriction level from the transfer permission map.
std::unique_lock<std::mutex> lock(m_mtxPermissionTransfer);
auto itTransferPermission = m_mapPermissionTransfer.find(tTransferID);
if (itTransferPermission == m_mapPermissionTransfer.end()) return 0u;
sdv::core::EAccessPermission ePermission = itTransferPermission->second;
m_mapPermissionTransfer.erase(itTransferPermission);
// Unlike the RestrictAccessPermission function, the SetAccessPermission function can be used to set an initial permission.
sdv::core::TPermissionID tPermissionID = m_idgen.Generate();
if (!tPermissionID) return 0u;
m_mapPermissions.emplace(tPermissionID, CreatePermissionObject(ePermission));
return tPermissionID;
}
sdv::core::EAccessPermission CPermissionControl::GetCurrentPermission() const
{
return m_lstPermissionTracker.empty() ? sdv::core::EAccessPermission::restricted_access : m_lstPermissionTracker.front().first;
}
CAccessPermission CPermissionControl::CreatePermissionObject(sdv::core::EAccessPermission ePermission)
{
sdv::core::EAccessPermission ePermissionLocal = std::max(ePermission, sdv::core::EAccessPermission::restricted_access);
return CAccessPermission(*this, ePermissionLocal);
}
void CPermissionControl::IncrementPermissionCount(sdv::core::EAccessPermission ePermission)
{
// In the permission tracker list, search for the counter for this permission or, if not existing, the next higher permission.
auto itPermissionCounter = m_lstPermissionTracker.begin();
for (; itPermissionCounter != m_lstPermissionTracker.end(); ++itPermissionCounter)
{
if (itPermissionCounter->first >= ePermission)
break;
}
// If counter doesn't exist, insert a new counter.
if (itPermissionCounter == m_lstPermissionTracker.end() || itPermissionCounter->first > ePermission)
itPermissionCounter = m_lstPermissionTracker.insert(itPermissionCounter, std::make_pair(ePermission, 1));
else // exists, increase the counter
itPermissionCounter->second++;
}
void CPermissionControl::DecrementPermissionCount(sdv::core::EAccessPermission ePermission)
{
// Find the permission counter
auto itPermissionCounter = std::find_if(m_lstPermissionTracker.begin(), m_lstPermissionTracker.end(), [&](const auto& rprPermissionCount)
{ return rprPermissionCount.first == ePermission; });
if (itPermissionCounter == m_lstPermissionTracker.end()) return;
// Reduce the counter and if reaching zero, erase the counter
itPermissionCounter->second--;
if (!itPermissionCounter->second)
m_lstPermissionTracker.erase(itPermissionCounter);
}

View File

@@ -0,0 +1,263 @@
/********************************************************************************
* 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:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef PERMISSION_CONTROL_H
#define PERMISSION_CONTROL_H
#include <interfaces/permission.h>
#include <list>
#include <support/component_impl.h>
#include "../../global/unique_id.h"
// Forward declaration
class CPermissionControl;
/**
* @brief Use a list to create a stack, which allows elements to be deleted.
*/
using TPermissionStack = std::list<sdv::core::EAccessPermission>;
/**
* @brief Permission class for the requested access. This class manages the lifetime of the permissions.
*/
class CAccessPermission
{
private:
/// Permission control is allowed to create an access permission object.
friend CPermissionControl;
/**
* @brief Constructor
* @param[in] rPermissionControl Reference to permission control.
* @param[in] ePermission The requested access permission.
*/
CAccessPermission(CPermissionControl& rPermissionControl, sdv::core::EAccessPermission ePermission);
public:
/**
* @brief Destructor
*/
~CAccessPermission();
/**
* @brief Copy constructor
* @param[in] rPermission Reference to the permission object to copy from.
*/
CAccessPermission(const CAccessPermission& rPermission);
/**
* @brief Move constructor.
* @param[in] rPermission Reference to the permission object to move from.
*/
CAccessPermission(CAccessPermission&& rPermission);
/**
* @brief Copy assignment operator.
* @param[in] rPermission Reference to the permission object to move from.
* @return Reference to this access permission object.
*/
CAccessPermission& operator=(const CAccessPermission& rPermission);
/**
* @brief Move assignment operator.
* @param[in] rPermission Reference to the permission object to move from.
* @return Reference to this access permission object.
*/
CAccessPermission& operator=(CAccessPermission&& rPermission);
/**
* @brief Get the permission level assigned to this object.
* @remarks This might not correspond to the current permission level available for this thread.
* @return The permission level.
*/
sdv::core::EAccessPermission Permission() const;
private:
CPermissionControl& m_rPermissionControl; ///< Reference to the permission control.
sdv::core::EAccessPermission m_ePermission; ///< Requested permission.
};
/**
* @brief Permission control provides a simple access mechanism to allow the management of access to system functions.
*/
class CPermissionControl : public sdv::IInterfaceAccess, public sdv::core::IPermissionControl
{
public:
/// Access permission class can access the permission control.
friend CAccessPermission;
/**
* @brief Default constructor
*/
CPermissionControl() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::core::IPermissionControl)
END_SDV_INTERFACE_MAP()
/**
* @brief Restrict the access permission for the current thread. Overload of
* sdv::core.:IPermissionControl::RestrictAccessPermission.
* @remarks The access restriction will be assigned to the current thread and combined with previous and future
* permissions. The lowest assigned permission will determine the actual access permission for the current thread.
* @remarks The access restriction will stay in effect until it is released by the function ReleaseAccessPermission.
* @remarks A newly created thread has fully restricted access. This cannot be changed using this function. Use an access
* restriction transfer from one thread to this thread to set a higher level of access permission.
* @param[in] ePermission The permission to restrict to.
* @return The permission ID for this restriction or 0 when the access permission could not be set. Use the
* ReleaseAccessPermission to release the restriction again.
*/
virtual sdv::core::TPermissionID RestrictAccessPermission(/*in*/ sdv::core::EAccessPermission ePermission) override;
/**
* @brief Release a previously set access restriction for the current thread. Overload of
* sdv::core::IPermissionControl::ReleaseAccessPermission.
* @param[in] tPermissionID The ID of the access restriction previously set for the current thread.
* @return Returns whether the restriction could be released successfully.
*/
virtual bool ReleaseAccessPermission(/*in*/ sdv::core::TPermissionID tPermissionID) override;
/**
* @brief Prepare to transfer the access restriction from the current thread.Overload of
* sdv::core.:IPermissionControl::TransferCurrentPermission.
* @return The ID of the transfer object containing the current access permissions or 0 when the transfer preparation has
* failed.
*/
virtual sdv::core::TPermissionTransferID TransferCurrentPermission() override;
/**
* @brief Set the access permission using a transfer object from one thread to another. Overload of
* sdv::core.:IPermissionControl::SetAccessPermission.
* @remarks An new thread has fully restricted access per default. Use this function to set the required access level. If a
* thread has already initialized with the proper access level, this function will set identical or lower access permissions
* for the current thread.
* @param[in] tTransferID The IS of the prepared access permission transfer.
* @return The permission ID for this restriction or 0 when the access permission could not be transferred. Use the
* ReleaseAccessPermission to release the restriction again.
*/
virtual sdv::core::TPermissionID SetAccessPermission(/*in*/ sdv::core::TPermissionTransferID tTransferID) override;
/**
* @brief Get the access permission level for the current thread. This will be the lowest restriction set for the current
* thread. Overload of sdv::core.:IPermissionControl::GetCurrentPermission.
* @return The current access permission level.
*/
virtual sdv::core::EAccessPermission GetCurrentPermission() const override;
/**
* @brief Create a permission object that assigns the access permissions to the call thread. The permissions stay in place
* until a last copy of the object is destroyed.
* @param[in] ePermission The new permission to set for the thread. If the permission has a value smaller than
* sdv::core::EAccessPermission::restricted_access, the object will be created with restricted access permission.
* @return The permission object. The permission will stay in place during the lifetime of the object.
*/
CAccessPermission CreatePermissionObject(sdv::core::EAccessPermission ePermission);
private:
/**
* @brief Increment the counter for a specific permission.
* @param[in] ePermission The permission the count should be incremented for.
*/
void IncrementPermissionCount(sdv::core::EAccessPermission ePermission);
/**
* @brief Increment the counter for a specific permission.
* @param[in] ePermission The permission the count should be incremented for.
*/
void DecrementPermissionCount(sdv::core::EAccessPermission ePermission);
CUniqueID<uint64_t> m_idgen; ///< Unique ID generator.
#if defined __GNUC__ && defined _WIN32
// https://dev.azure.com/SW4ZF/AZP-431_DivDI_Vehicle_API/_workitems/edit/610009
// In MINGW GCC implementation of thread_local, there is an issue with complex classes being used after the destruction. This
// is caused by an architectural bug (https://github.com/msys2/MINGW-packages/issues/2519) causing the emutls (GCC Emulated TLS)
// managing the memory allocation to be executed before the __cxa_thread_exit, the thread cleanup callback of Windows.
// The solution is to use trivial data only (pointer, reference, integers, etc.). A workaround is implemented to use a reference
// of the permission list and map instead of an instance to the list and map.
/**
* @brief Create a permission list object.
* @return Reference to the created list object.
*/
static std::list<std::pair<sdv::core::EAccessPermission, size_t>>& CreatePermissionList()
{
static std::mutex mtxPermissionLists;
static std::list<std::list<std::pair<sdv::core::EAccessPermission, size_t>>> lstPermissionLists;
std::unique_lock<std::mutex> lock(mtxPermissionLists);
lstPermissionLists.resize(lstPermissionLists.size() + 1);
return lstPermissionLists.back();
}
/// Thread specific permission tracker list (permission, lowest first and the count for each permission second).
static thread_local std::list<std::pair<sdv::core::EAccessPermission, size_t>>& m_lstPermissionTracker;
/**
* @brief Create a permission map object.
* @return Reference to the created map object.
*/
static std::map<sdv::core::TPermissionID, CAccessPermission>& CreatePermissionMap()
{
static std::mutex mtxPermissionMaps;
static std::list<std::map<sdv::core::TPermissionID, CAccessPermission>> lstPermissionMaps;
std::unique_lock<std::mutex> lock(mtxPermissionMaps);
lstPermissionMaps.resize(lstPermissionMaps.size() + 1);
return lstPermissionMaps.back();
}
/// Thread specific permission access map.
static thread_local std::map<sdv::core::TPermissionID, CAccessPermission>& m_mapPermissions;
#else
/// Thread specific permission tracker list (permission, lowest first and the count for each permission second).
static thread_local std::list<std::pair<sdv::core::EAccessPermission, size_t>> m_lstPermissionTracker;
/// Thread specific permission access map.
static thread_local std::map<sdv::core::TPermissionID, CAccessPermission> m_mapPermissions;
#endif
/// Permission transfer object map (allow shifting permission from one thread to another).
std::map<sdv::core::TPermissionTransferID, sdv::core::EAccessPermission> m_mapPermissionTransfer;
std::mutex m_mtxPermissionTransfer; ///< Protecting the permission transfer map.
};
/**
* @brief Return the permission control.
* @return Reference to the permission control.
*/
CPermissionControl& GetPermissionControl();
/**
* @brief Permission control service
*/
class CPermissionControlService : public sdv::CSdvObject
{
public:
CPermissionControlService() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_CHAIN_MEMBER(GetPermissionControl())
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("PermissionControlService")
DECLARE_OBJECT_SINGLETON()
};
DEFINE_SDV_OBJECT(CPermissionControlService)
#endif // !defined PERMISSION_CONTROL_H

View File

@@ -16,12 +16,14 @@
#include <iostream>
#include <cassert>
#include <algorithm>
#include <optional>
#include "object_lifetime_control.h"
#include "../../global/base64.h"
#include "module_control.h"
#include "app_config.h"
#include "app_control.h"
#include "app_settings.h"
#include "permission_control.h"
// GetRepository might be redirected for unit tests.
#ifndef GetRepository
@@ -60,39 +62,154 @@ void CRepository::SetRunningMode()
sdv::IInterfaceAccess* CRepository::GetObject(const sdv::u8string& ssObjectName)
{
auto eAccessPermission = GetPermissionControl().GetCurrentPermission();
if (eAccessPermission < sdv::core::EAccessPermission::remote_access)
return nullptr;
std::shared_lock<std::shared_mutex> lock(m_mtxObjects);
auto itService = m_mapServiceObjects.find(ssObjectName);
if (itService != m_mapServiceObjects.end()) return (*itService->second)->ptrObject;
if (itService != m_mapServiceObjects.end())
{
// Check access permissions
switch ((*itService->second)->sClassInfo.eType)
{
case sdv::EObjectType::complex_service:
case sdv::EObjectType::vehicle_function:
case sdv::EObjectType::basic_service:
case sdv::EObjectType::sensor:
case sdv::EObjectType::actuator:
// Allowed for remote access or higher
//if (eAccessPermission < sdv::core::EAccessPermission::remote_access)
// return nullptr;
break;
case sdv::EObjectType::system_object:
// Allowed for local access or higher
if (eAccessPermission < sdv::core::EAccessPermission::local_access)
return nullptr;
break;
default:
// Allowed for full access only
if (eAccessPermission != sdv::core::EAccessPermission::full_access)
return nullptr;
}
return (*itService->second)->ptrObject;
}
lock.unlock();
// In case the object is not in the service map and this is a main or isolated application, create the object if the object is
// known in the installation manifest and is a system object.
// known in the installation manifest.
auto optManifest = GetAppConfig().FindInstalledComponent(ssObjectName);
if (optManifest && optManifest->eType == sdv::EObjectType::system_object)
return GetObjectByID(CreateObject(optManifest->ssName, optManifest->ssDefaultObjectName, ""));
if (optManifest)
{
// Check access permissions
bool bAutoCreate = false;
switch (optManifest->eType)
{
case sdv::EObjectType::complex_service:
case sdv::EObjectType::vehicle_function:
case sdv::EObjectType::basic_service:
case sdv::EObjectType::sensor:
case sdv::EObjectType::actuator:
// Automatic component creation only for main application.
if (!GetAppSettings().IsMainApplication()) break;
bAutoCreate = true;
// Allowed for remote access or higher
if (eAccessPermission < sdv::core::EAccessPermission::remote_access)
return nullptr;
break;
case sdv::EObjectType::system_object:
// Automatic component creation allowed for system objects. The objects themselves have to block in case of unwanted
// access by external or isolated applications.
bAutoCreate = true;
// Forward the request to core repository if one is linked here (this can only occur with isolated and external applications).
if (!m_ptrCoreRepoAccess) return nullptr;
sdv::core::IObjectAccess* pObjectAccess = m_ptrCoreRepoAccess.GetInterface<sdv::core::IObjectAccess>();
if (!pObjectAccess) return nullptr;
return pObjectAccess->GetObject(ssObjectName);
// Allowed for local access or higher
if (eAccessPermission < sdv::core::EAccessPermission::local_access)
return nullptr;
break;
default:
// Automatic component creation only for main application.
if (!GetAppSettings().IsMainApplication())
break;
bAutoCreate = true;
// Allowed for full access only
if (eAccessPermission != sdv::core::EAccessPermission::full_access)
return nullptr;
}
// Create the object if automatic creation is enabled
if (bAutoCreate)
return GetObjectByID(CreateObject2(optManifest->ssName, optManifest->ssDefaultObjectName, "", true));
}
// Copy the linked repository and then unlock; could cause a deadlock instead.
lock.lock();
auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess;
lock.unlock();
// If there is a linked core, use the core to get ther object
for (auto& rprLink : lstCoreRepoAccessCopy)
{
sdv::core::IObjectAccess* pObjectAccess = rprLink.second.GetInterface<sdv::core::IObjectAccess>();
if (!pObjectAccess) continue;
auto* pObject = pObjectAccess->GetObject(ssObjectName);
if (pObject) return pObject;
}
return nullptr;
}
sdv::IInterfaceAccess* CRepository::GetObjectByID(/*in*/ sdv::core::TObjectID tObjectID)
{
auto eAccessPermission = GetPermissionControl().GetCurrentPermission();
if (eAccessPermission < sdv::core::EAccessPermission::remote_access)
return nullptr;
// Only controlled objects are allowed to be returned using GetObjectByID.
std::shared_lock<std::shared_mutex> lock(m_mtxObjects);
auto itObject = m_mapObjects.find(tObjectID);
if (itObject != m_mapObjects.end())
return itObject->second->bControlled ? itObject->second->ptrObject : nullptr;
{
// Check access permissions
switch (itObject->second->sClassInfo.eType)
{
case sdv::EObjectType::complex_service:
case sdv::EObjectType::vehicle_function:
case sdv::EObjectType::basic_service:
case sdv::EObjectType::sensor:
case sdv::EObjectType::actuator:
//// Allowed for remote access or higher
//if (eAccessPermission < sdv::core::EAccessPermission::remote_access)
// return nullptr;
break;
case sdv::EObjectType::system_object:
// Allowed for local access or higher
if (eAccessPermission < sdv::core::EAccessPermission::local_access)
return nullptr;
break;
default:
// Allowed for full access only
if (eAccessPermission != sdv::core::EAccessPermission::full_access)
return nullptr;
}
return itObject->second->bControlled ? itObject->second->ptrObject : nullptr;
}
// TODO: Deal with overlapping IDs in this and in core process...
// Forward the request to core repository if one is linked here (this can only occur with isolated and external applications).
if (!m_ptrCoreRepoAccess) return nullptr;
sdv::core::IObjectAccess* pObjectAccess = m_ptrCoreRepoAccess.GetInterface<sdv::core::IObjectAccess>();
if (!pObjectAccess) return nullptr;
return pObjectAccess->GetObjectByID(tObjectID);
// Copy the linked repository and then unlock; could cause a deadlock instead.
auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess;
lock.unlock();
// If there is a linked core, use the core to get ther object
for (auto& rprLink : lstCoreRepoAccessCopy)
{
sdv::core::IObjectAccess* pObjectAccess = rprLink.second.GetInterface<sdv::core::IObjectAccess>();
if (!pObjectAccess) continue;
auto* pObject = pObjectAccess->GetObjectByID(tObjectID);
if (pObject) return pObject;
}
return nullptr;
}
sdv::IInterfaceAccess* CRepository::CreateUtility(/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectConfig)
@@ -101,6 +218,11 @@ sdv::IInterfaceAccess* CRepository::CreateUtility(/*in*/ const sdv::u8string& ss
std::unique_lock<std::shared_mutex> lock(m_mtxObjects);
// TODO EVE: Currently only allowed for local access. When utilities are instantiated using isolation, also allowed for remote
// access.
if (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::local_access)
return nullptr;
// Get a fitting module instance
std::shared_ptr<CModuleInst> ptrModule = GetModuleControl().FindModuleByClass(ssClassName);
if (!ptrModule)
@@ -161,7 +283,7 @@ sdv::IInterfaceAccess* CRepository::CreateUtility(/*in*/ const sdv::u8string& ss
auto* pObjectControl = ptrObject.GetInterface<sdv::IObjectControl>();
if (pObjectControl)
{
pObjectControl->Initialize(ssObjectConfig);
pObjectControl->Initialize(*ptrObjectEntry);
if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized)
{
// Destroy the object
@@ -193,22 +315,23 @@ sdv::IInterfaceAccess* CRepository::CreateProxyObject(/*in*/ sdv::interface_id i
// Get a fitting module instance
std::string ssClassName = "Proxy_" + std::to_string(id);
std::shared_ptr<CModuleInst> ptrModule = GetModuleControl().FindModuleByClass(ssClassName);
if (!ptrModule && m_ptrCoreRepoAccess)
{
// Request the server for the name of the module.
const sdv::core::IRepositoryInfo* pRepInfo = m_ptrCoreRepoAccess.GetInterface<sdv::core::IRepositoryInfo>();
if (pRepInfo)
{
std::string ssModuleName =
std::filesystem::u8path(static_cast<std::string>(pRepInfo->FindClass(ssClassName).ssModulePath)).
filename().u8string();
if (!ssModuleName.empty())
{
sdv::core::TModuleID tModule = GetModuleControl().Load(ssModuleName);
if (tModule) ptrModule = GetModuleControl().GetModule(tModule);
}
}
}
// TODO EVE
//if (!ptrModule && m_ptrCoreRepoAccess)
//{
// // Request the server for the name of the module.
// const sdv::core::IRepositoryInfo* pRepInfo = m_ptrCoreRepoAccess.GetInterface<sdv::core::IRepositoryInfo>();
// if (pRepInfo)
// {
// std::string ssModuleName =
// std::filesystem::u8path(static_cast<std::string>(pRepInfo->FindClass(ssClassName).ssModulePath)).
// filename().u8string();
// if (!ssModuleName.empty())
// {
// sdv::core::TModuleID tModule = GetModuleControl().Load(ssModuleName);
// if (tModule) ptrModule = GetModuleControl().GetModule(tModule);
// }
// }
//}
if (!ptrModule)
{
SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!");
@@ -270,7 +393,7 @@ sdv::IInterfaceAccess* CRepository::CreateProxyObject(/*in*/ sdv::interface_id i
auto* pObjectControl = ptrObject.GetInterface<sdv::IObjectControl>();
if (pObjectControl)
{
pObjectControl->Initialize("");
pObjectControl->Initialize(*ptrObjectEntry);
if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized)
{
// Destroy the object
@@ -302,22 +425,23 @@ sdv::IInterfaceAccess* CRepository::CreateStubObject(/*in*/ sdv::interface_id id
// Get a fitting module instance
std::string ssClassName = "Stub_" + std::to_string(id);
std::shared_ptr<CModuleInst> ptrModule = GetModuleControl().FindModuleByClass(ssClassName);
if (!ptrModule && m_ptrCoreRepoAccess)
{
// Request the server for the name of the module.
const sdv::core::IRepositoryInfo* pRepInfo = m_ptrCoreRepoAccess.GetInterface<sdv::core::IRepositoryInfo>();
if (pRepInfo)
{
std::string ssModuleName =
std::filesystem::u8path(static_cast<std::string>(pRepInfo->FindClass(ssClassName).ssModulePath)).
filename().u8string();
if (!ssModuleName.empty())
{
sdv::core::TModuleID tModule = GetModuleControl().Load(ssModuleName);
if (tModule) ptrModule = GetModuleControl().GetModule(tModule);
}
}
}
// TODO EVE
//if (!ptrModule && m_ptrCoreRepoAccess)
//{
// // Request the server for the name of the module.
// const sdv::core::IRepositoryInfo* pRepInfo = m_ptrCoreRepoAccess.GetInterface<sdv::core::IRepositoryInfo>();
// if (pRepInfo)
// {
// std::string ssModuleName =
// std::filesystem::u8path(static_cast<std::string>(pRepInfo->FindClass(ssClassName).ssModulePath)).
// filename().u8string();
// if (!ssModuleName.empty())
// {
// sdv::core::TModuleID tModule = GetModuleControl().Load(ssModuleName);
// if (tModule) ptrModule = GetModuleControl().GetModule(tModule);
// }
// }
//}
if (!ptrModule)
{
SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!");
@@ -378,7 +502,7 @@ sdv::IInterfaceAccess* CRepository::CreateStubObject(/*in*/ sdv::interface_id id
auto* pObjectControl = ptrObject.GetInterface<sdv::IObjectControl>();
if (pObjectControl)
{
pObjectControl->Initialize("");
pObjectControl->Initialize(*ptrObjectEntry);
if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized)
{
// Destroy the object
@@ -414,11 +538,11 @@ sdv::core::TObjectID CRepository::CreateObject(const sdv::u8string& ssClassName,
Build dependency list and do these checks on dependent objects as well
*/
return CreateObject2(ssClassName, ssObjectName, ssObjectConfig);
return CreateObject2(ssClassName, ssObjectName, ssObjectConfig, false);
}
sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName, const sdv::u8string& ssObjectName,
const sdv::u8string& ssObjectConfig)
const sdv::u8string& ssObjectConfig, bool bIndirect)
{
// TODO EVE: Link to core repo. Allow only creation of one object if not already created before...
// Add support for automatic app control shutdown when utility is closed in case of isolated app.
@@ -457,21 +581,29 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName
switch (optClassInfo->eType)
{
case sdv::EObjectType::system_object:
// Only allowed with full access
bError = !bIndirect && (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::full_access);
break;
case sdv::EObjectType::device:
case sdv::EObjectType::platform_abstraction:
case sdv::EObjectType::vehicle_bus:
bError = !bDeviceAndBasicServiceAllowed;
// Only allowed with full access
bError = !bIndirect && (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::full_access);
bError |= !bDeviceAndBasicServiceAllowed;
break;
case sdv::EObjectType::basic_service:
case sdv::EObjectType::sensor:
case sdv::EObjectType::actuator:
bError = !bDeviceAndBasicServiceAllowed;
// Only allowed with full access
bError = !bIndirect && (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::full_access);
bError |= !bDeviceAndBasicServiceAllowed;
break;
case sdv::EObjectType::complex_service:
case sdv::EObjectType::vehicle_function:
// Only allowed with local access
bError = !bIndirect && (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::local_access);
bIsolate = GetAppSettings().IsMainApplication();
bError = !bComplexServiceAllowed;
bError |= !bComplexServiceAllowed;
m_bIsoObjectLoaded = true;
break;
default:
@@ -498,8 +630,11 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName
auto itPreviousService = m_mapServiceObjects.find(ssObjectName2);
if (itPreviousService != m_mapServiceObjects.end())
{
// Get the service pointer
std::shared_ptr<SObjectEntry> ptrService = *itPreviousService->second;
// Object entry is valid?
if (!*itPreviousService->second)
if (!ptrService)
{
// This should not occur... there is a previous object in the service map, but the object is empty.
SDV_LOG_ERROR("Object creation requested for class \"", ssClassName, "\", but object with the same name \"",
@@ -508,12 +643,15 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName
}
// Trying to create an object with the same name is not an error if the classes are identical.
if ((*itPreviousService->second)->sClassInfo.ssName == ssClassName)
return (*itPreviousService->second)->tObjectID;
const auto& rsClassInfo = ptrService->sClassInfo;
if (rsClassInfo.ssName == ssClassName ||
std::find(rsClassInfo.seqClassAliases.begin(), rsClassInfo.seqClassAliases.end(), ssClassName) !=
rsClassInfo.seqClassAliases.end())
return ptrService->tObjectID;
// Object name was already used by another class. This is an error.
SDV_LOG_ERROR("Object creation requested for class \"", ssClassName, "\", but object with the same name \"",
ssObjectName2, "\" was already instantiated for class \"", (*itPreviousService->second)->sClassInfo.ssName,
ssObjectName2, "\" was already instantiated for class \"", rsClassInfo.ssName,
"\"!");
return 0;
}
@@ -549,14 +687,18 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName
sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModuleID tModuleID,
/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssObjectConfig)
{
if(ssClassName.empty()) return false;
if(ssClassName.empty()) return 0u;
// Only allowed with full access
if (GetPermissionControl().GetCurrentPermission() != sdv::core::EAccessPermission::full_access)
return 0u;
// Get a fitting module instance
std::shared_ptr<CModuleInst> ptrModule = GetModuleControl().GetModule(tModuleID);
if (!ptrModule)
{
SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!");
return false;
return 0u;
}
// Check the class type
@@ -564,7 +706,7 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu
if (!optClassInfo)
{
SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!");
return false;
return 0u;
}
bool bError = false;
bool bDeviceAndBasicServiceAllowed = GetAppSettings().IsMainApplication() || GetAppSettings().IsStandaloneApplication() ||
@@ -602,7 +744,7 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu
// Utilities and marshall objects cannot be created using the CreateObject function.
SDV_LOG_ERROR("Creation of an object of invalid type \"", sdv::ObjectType2String(optClassInfo->eType),
"\" requested for class \"", ssClassName, "\"!");
return 0;
return 0u;
}
// Check for an object name. If not existing get the default name (being either one specified by the object or the class name).
@@ -626,7 +768,7 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu
"\", but object with the same name \"",
ssObjectName2,
"\" was already instantiated, but cannot be found!");
return 0;
return 0u;
}
// Trying to create an object with the same name is not an error if the classes are identical.
@@ -641,7 +783,7 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu
"\" was already instantiated for class \"",
(*itPreviousService->second)->sClassInfo.ssName,
"\"!");
return 0;
return 0u;
}
// Check with singleton objects if the object was already instantiated.
@@ -657,7 +799,7 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu
SDV_LOG_ERROR("Object creation requested but object from the same class \"",
ssClassName,
"\" was already instantiated and only one instance is allowed!");
return 0;
return 0u;
}
}
}
@@ -671,17 +813,41 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu
bool CRepository::DestroyObject(/*in*/ const sdv::u8string& ssObjectName)
{
return DestroyObject2(ssObjectName);
TDeferredObjectDestructionList lstDeferredObjectDestruction;
bool bRes = DestroyObject2(ssObjectName, false, lstDeferredObjectDestruction);
for (auto& rprObject : lstDeferredObjectDestruction)
rprObject.first->ptrModule->DestroyObject(rprObject.second);
return bRes;
}
bool CRepository::DestroyObject2(/*in*/ const sdv::u8string& ssObjectName)
bool CRepository::DestroyObject2(const sdv::u8string& ssObjectName, bool bIndirect,
TDeferredObjectDestructionList& rlstDeferredObjectDestruction)
{
std::unique_lock<std::shared_mutex> lock(m_mtxObjects);
auto itService = m_mapServiceObjects.find(ssObjectName);
if (itService == m_mapServiceObjects.end()) return false;
auto ptrObjectEntry = *itService->second;
// When directly called, check the access permissions
if (!bIndirect)
{
switch (ptrObjectEntry->sClassInfo.eType)
{
case sdv::EObjectType::complex_service:
case sdv::EObjectType::vehicle_function:
// Only allowed having local or higher access permission
if (GetPermissionControl().GetCurrentPermission() < sdv::core::EAccessPermission::local_access)
return false;
break;
default:
// Only allowed having full access permission
if (GetPermissionControl().GetCurrentPermission() != sdv::core::EAccessPermission::full_access)
return false;
break;
}
}
// Print info
auto ptrObjectEntry = *itService->second;
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Destroy a " << sdv::ObjectType2String(ptrObjectEntry->sClassInfo.eType) << " #" << ptrObjectEntry->tObjectID <<
" of type " << ptrObjectEntry->sClassInfo.ssName << " with the name " << ssObjectName << std::endl;
@@ -717,13 +883,25 @@ bool CRepository::DestroyObject2(/*in*/ const sdv::u8string& ssObjectName)
lock.unlock();
// Only controlled objects can be destroyed this way.
if (ptrDependingObject->bControlled) DestroyObject(ptrDependingObject->ssName);
if (ptrDependingObject->bControlled) DestroyObject2(ptrDependingObject->ssName, false, rlstDeferredObjectDestruction);
}
};
// Static dependance from object class
fnDestroyDependingObjects(GetDependingObjectInstancesByClass(ptrObjectEntry->sClassInfo.ssName));
if (ptrObjectEntry->sClassInfo.ssName != ptrObjectEntry->sClassInfo.ssDefaultObjectName)
fnDestroyDependingObjects(GetDependingObjectInstancesByClass(ptrObjectEntry->sClassInfo.ssDefaultObjectName));
// Dynamic dependance from object entry
std::vector<sdv::core::TObjectID> vecDependingObjects;
for (const std::string& rssDependingObject : ptrObjectEntry->setDependentObjects)
{
auto itObject = m_mapServiceObjects.find(rssDependingObject);
if (itObject != m_mapServiceObjects.end())
vecDependingObjects.push_back((*itObject->second)->tObjectID);
}
fnDestroyDependingObjects(vecDependingObjects);
// Shutdown the object (not all objects expose IObjectControl).
auto* pObjectControl = ptrObjectEntry->ptrObject.GetInterface<sdv::IObjectControl>();
if (pObjectControl && pObjectControl->GetObjectState() != sdv::EObjectState::destruction_pending)
@@ -735,7 +913,8 @@ bool CRepository::DestroyObject2(/*in*/ const sdv::u8string& ssObjectName)
sdv::IInterfaceAccess* pObject = ptrObjectEntry->ptrObject;
if (ptrObjectEntry->ptrIsoMon)
pObject = ptrObjectEntry->ptrIsoMon->GetContainedObject();
ptrObjectEntry->ptrModule->DestroyObject(pObject);
rlstDeferredObjectDestruction.push_back(std::make_pair(ptrObjectEntry, pObject));
//ptrObjectEntry->ptrModule->DestroyObject(pObject);
}
return true;
@@ -805,15 +984,22 @@ sdv::core::TObjectID CRepository::RegisterObject(IInterfaceAccess* pObjectIfc, c
return tObjectID;
}
void CRepository::LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository)
sdv::core::TLinkID CRepository::LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository)
{
if (!pCoreRepository) return;
m_ptrCoreRepoAccess = pCoreRepository;
if (!pCoreRepository) return 0;
std::unique_lock<std::shared_mutex> lock(m_mtxObjects);
sdv::core::TLinkID tLinkID = CreateObjectID();
m_lstCoreRepoAccess.push_back(std::make_pair(tLinkID, pCoreRepository));
return tLinkID;
}
void CRepository::UnlinkCoreRepository()
void CRepository::UnlinkCoreRepository(sdv::core::TLinkID tLinkID)
{
m_ptrCoreRepoAccess = nullptr;
std::unique_lock<std::shared_mutex> lock(m_mtxObjects);
auto itLink = std::find_if(
m_lstCoreRepoAccess.begin(), m_lstCoreRepoAccess.end(), [&](const auto rprLink)
{ return rprLink.first == tLinkID; });
m_lstCoreRepoAccess.erase(itLink);
}
sdv::SClassInfo CRepository::FindClass(/*in*/ const sdv::u8string& ssClassName) const
@@ -824,16 +1010,34 @@ sdv::SClassInfo CRepository::FindClass(/*in*/ const sdv::u8string& ssClassName)
if (GetAppSettings().IsMainApplication() || GetAppSettings().IsIsolatedApplication())
{
auto optManifest = GetAppConfig().FindInstalledComponent(ssClassName);
if (!optManifest) return {};
return *optManifest;
if (optManifest)
return *optManifest;
}
else
{
// Get the information through the module.
auto ptrModule = GetModuleControl().FindModuleByClass(ssClassName);
if (ptrModule)
{
auto optClassInfo = ptrModule->GetClassInfo(ssClassName);
if (optClassInfo) return *optClassInfo;
}
}
// Get the information through the module.
auto ptrModule = GetModuleControl().FindModuleByClass(ssClassName);
if (!ptrModule) return {};
auto optClassInfo = ptrModule->GetClassInfo(ssClassName);
if (!optClassInfo) return {};
return *optClassInfo;
// Copy the linked repository and then unlock; could cause a deadlock instead.
auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess;
lock.unlock();
// If there is a linked core, use the core to get class list
for (auto& rprLink : lstCoreRepoAccessCopy)
{
const sdv::core::IRepositoryInfo* pCoreRepoInfo = rprLink.second.GetInterface<sdv::core::IRepositoryInfo>();
sdv::SClassInfo sInfo = pCoreRepoInfo->FindClass(ssClassName);
if (!sInfo.ssName.empty()) return sInfo;
}
// Nothing found
return {};
}
sdv::sequence<sdv::core::SObjectInfo> CRepository::GetObjectList() const
@@ -841,13 +1045,6 @@ sdv::sequence<sdv::core::SObjectInfo> CRepository::GetObjectList() const
std::shared_lock<std::shared_mutex> lock(m_mtxObjects);
sdv::sequence<sdv::core::SObjectInfo> seqObjects;
// If there is a linked core, use the core to get object list
if (m_ptrCoreRepoAccess)
{
const sdv::core::IRepositoryInfo* pCoreRepoInfo = m_ptrCoreRepoAccess.GetInterface<sdv::core::IRepositoryInfo>();
return pCoreRepoInfo ? pCoreRepoInfo->GetObjectList() : seqObjects;
}
// Add object function
auto fnAddObject = [&seqObjects](const std::shared_ptr<SObjectEntry>& rptrObjectEntry)
{
@@ -878,6 +1075,21 @@ sdv::sequence<sdv::core::SObjectInfo> CRepository::GetObjectList() const
for (const auto& prLocalObject : m_mapLocalObjects)
fnAddObject(prLocalObject.second);
// Copy the linked repository and then unlock; could cause a deadlock instead.
auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess;
lock.unlock();
// If there is a linked core, use the core to get object list
for (auto& rprLink : lstCoreRepoAccessCopy)
{
const sdv::core::IRepositoryInfo* pCoreRepoInfo = rprLink.second.GetInterface<sdv::core::IRepositoryInfo>();
if (pCoreRepoInfo)
{
auto seqListRemote = pCoreRepoInfo->GetObjectList();
seqObjects.insert(seqObjects.end(), seqListRemote.begin(), seqListRemote.end());
}
}
return seqObjects;
}
@@ -885,54 +1097,78 @@ sdv::core::SObjectInfo CRepository::GetObjectInfo(/*in*/ sdv::core::TObjectID tO
{
std::shared_lock<std::shared_mutex> lock(m_mtxObjects);
// If there is a linked core, use the core to get object info
if (m_ptrCoreRepoAccess)
// Use the internal object map for this.
sdv::core::SObjectInfo sInfo{};
auto itObject = m_mapObjects.find(tObjectID);
if (itObject != m_mapObjects.end())
{
const sdv::core::IRepositoryInfo* pCoreRepoInfo = m_ptrCoreRepoAccess.GetInterface<sdv::core::IRepositoryInfo>();
return pCoreRepoInfo ? pCoreRepoInfo->GetObjectInfo(tObjectID) : sdv::core::SObjectInfo{};
if (!itObject->second) return {};
// Fill in information
sInfo.tObjectID = itObject->second->tObjectID;
sInfo.tModuleID = itObject->second->ptrModule ? itObject->second->ptrModule->GetModuleID() : 0;
sInfo.sClassInfo = itObject->second->sClassInfo;
sInfo.ssObjectName = itObject->second->ssName;
sInfo.ssObjectConfig = itObject->second->ssConfig;
sInfo.uiFlags = 0;
if (itObject->second->bControlled)
sInfo.uiFlags |= static_cast<uint32_t>(sdv::core::EObjectInfoFlags::object_controlled);
if (!itObject->second->ptrModule)
sInfo.uiFlags |= static_cast<uint32_t>(sdv::core::EObjectInfoFlags::object_foreign);
if (!itObject->second->bIsolated)
sInfo.uiFlags |= static_cast<uint32_t>(sdv::core::EObjectInfoFlags::object_isolated);
return sInfo;
}
// Use the internal object map for this.
auto itObject = m_mapObjects.find(tObjectID);
if (itObject == m_mapObjects.end()) return {};
if (!itObject->second) return {};
// Copy the linked repository and then unlock; could cause a deadlock instead.
auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess;
lock.unlock();
// Fill in information
sdv::core::SObjectInfo sInfo{};
sInfo.tObjectID = itObject->second->tObjectID;
sInfo.tModuleID = itObject->second->ptrModule ? itObject->second->ptrModule->GetModuleID() : 0;
sInfo.sClassInfo = itObject->second->sClassInfo;
sInfo.ssObjectName = itObject->second->ssName;
sInfo.ssObjectConfig = itObject->second->ssConfig;
sInfo.uiFlags = 0;
if (itObject->second->bControlled)
sInfo.uiFlags |= static_cast<uint32_t>(sdv::core::EObjectInfoFlags::object_controlled);
if (!itObject->second->ptrModule)
sInfo.uiFlags |= static_cast<uint32_t>(sdv::core::EObjectInfoFlags::object_foreign);
if (!itObject->second->bIsolated)
sInfo.uiFlags |= static_cast<uint32_t>(sdv::core::EObjectInfoFlags::object_isolated);
// If there is a linked core, use the core to get object info
for (auto& rprLink : lstCoreRepoAccessCopy)
{
const sdv::core::IRepositoryInfo* pCoreRepoInfo = rprLink.second.GetInterface<sdv::core::IRepositoryInfo>();
if (pCoreRepoInfo)
{
sInfo = pCoreRepoInfo->GetObjectInfo(tObjectID);
if (sInfo.tObjectID == tObjectID) return sInfo;
}
}
return sInfo;
// Info not found
return {};
}
sdv::core::SObjectInfo CRepository::FindObject(/*in*/ const sdv::u8string& ssObjectName) const
{
std::shared_lock<std::shared_mutex> lock(m_mtxObjects);
// If there is a linked core, use the core to get object info
if (m_ptrCoreRepoAccess)
{
const sdv::core::IRepositoryInfo* pCoreRepoInfo = m_ptrCoreRepoAccess.GetInterface<sdv::core::IRepositoryInfo>();
return pCoreRepoInfo ? pCoreRepoInfo->FindObject(ssObjectName) : sdv::core::SObjectInfo{};
}
// Use internal service map for this.
auto itService = m_mapServiceObjects.find(ssObjectName);
if (itService == m_mapServiceObjects.end()) return {};
auto ptrObjectEntry = *itService->second;
if (!ptrObjectEntry) return {};
if (itService != m_mapServiceObjects.end())
{
auto ptrObjectEntry = *itService->second;
if (!ptrObjectEntry) return {};
return GetObjectInfo(ptrObjectEntry->tObjectID);
}
// Copy the linked repository and then unlock; could cause a deadlock instead.
auto lstCoreRepoAccessCopy = m_lstCoreRepoAccess;
lock.unlock();
return GetObjectInfo(ptrObjectEntry->tObjectID);
// If there is a linked core, use the core to get object info
for (auto& rprLink : lstCoreRepoAccessCopy)
{
const sdv::core::IRepositoryInfo* pCoreRepoInfo = rprLink.second.GetInterface<sdv::core::IRepositoryInfo>();
if (pCoreRepoInfo)
{
sdv::core::SObjectInfo sInfo = pCoreRepoInfo->FindObject(ssObjectName);
if (sInfo.tObjectID) return sInfo;
}
}
return {};
}
void CRepository::OnDestroyObject(sdv::IInterfaceAccess* pObject)
@@ -998,9 +1234,10 @@ void CRepository::DestroyAllObjects(const std::vector<std::string>& rvecIgnoreOb
lock.unlock();
// Destroy the objects in reverse order
TDeferredObjectDestructionList lstDeferredObjectDestruction;
while (lstCopy.size())
{
std::string ssObjectName = lstCopy.back() ? lstCopy.back()->ssName : std::string{};
sdv::u8string ssObjectName = lstCopy.back() ? lstCopy.back()->ssName : sdv::u8string{};
lstCopy.pop_back();
if (ssObjectName.empty()) continue;
@@ -1009,7 +1246,7 @@ void CRepository::DestroyAllObjects(const std::vector<std::string>& rvecIgnoreOb
continue;
// Destroy the object... -> call the repository function to remove it from the list.
DestroyObject(ssObjectName);
DestroyObject2(ssObjectName, true, lstDeferredObjectDestruction);
// Needs force!
if (bForce)
@@ -1038,6 +1275,34 @@ void CRepository::DestroyAllObjects(const std::vector<std::string>& rvecIgnoreOb
lock.unlock();
}
}
for (auto& rprObject : lstDeferredObjectDestruction)
rprObject.first->ptrModule->DestroyObject(rprObject.second);
}
void CRepository::AddObjectDependency(/*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssDependsOnObject)
{
std::unique_lock<std::shared_mutex> lock(m_mtxObjects);
auto itService = m_mapServiceObjects.find(ssDependsOnObject);
if (itService != m_mapServiceObjects.end())
{
auto ptrObjectEntry = *itService->second;
if (!ptrObjectEntry) return;
auto itDependsOn = ptrObjectEntry->setDependentObjects.find(ssObjectName);
if (itDependsOn == ptrObjectEntry->setDependentObjects.end())
ptrObjectEntry->setDependentObjects.insert(ssObjectName);
}
}
void CRepository::RemoveObjectDependency(/*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssDependsOnObject)
{
std::unique_lock<std::shared_mutex> lock(m_mtxObjects);
auto itService = m_mapServiceObjects.find(ssDependsOnObject);
if (itService != m_mapServiceObjects.end())
{
auto ptrObjectEntry = *itService->second;
if (!ptrObjectEntry) return;
ptrObjectEntry->setDependentObjects.erase(ssObjectName);
}
}
void CRepository::ResetConfigBaseline()
@@ -1100,8 +1365,9 @@ sdv::core::EConfigProcessResult CRepository::StartFromConfig(const CAppConfigFil
// Destroy the objects when an error occurred during loading.
if (!bAllowPartialLoad && nFail)
{
TDeferredObjectDestructionList lstDeferredObjectDestruction;
for (const std::string& rssName : vecLoadedObjects)
DestroyObject(rssName);
DestroyObject2(rssName, true, lstDeferredObjectDestruction);
return sdv::core::EConfigProcessResult::failed;
}
@@ -1185,11 +1451,14 @@ sdv::core::TObjectID CRepository::CreateIsolatedObject(const sdv::SClassInfo& rs
sdv::TInterfaceAccessPtr(GetObject("CommunicationControl")).GetInterface<sdv::com::IConnectionControl>();
if (!pConnectionControl) return 0;
sdv::u8string ssConnectionString;
std::optional<CAccessPermission> optPermissionObject =
GetPermissionControl().CreatePermissionObject(sdv::core::EAccessPermission::local_access);
sdv::com::TConnectionID tConnection =
pConnectionControl->CreateServerConnection(sdv::com::EChannelType::local_channel,
sdv::core::GetObject("RepositoryService"), 5000, ssConnectionString);
if (!tConnection.uiControl) return 0;
if (ssConnectionString.empty()) return 0;
optPermissionObject.reset();
// Create the isolation process configuration
std::stringstream sstreamConfig;
@@ -1298,7 +1567,10 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr<CMo
if (!ptrObject)
{
// Destroy the object again
DestroyObject(rssObjectName);
TDeferredObjectDestructionList lstDeferredObjectDestruction;
DestroyObject2(rssObjectName, true, lstDeferredObjectDestruction);
for (auto& rprObject : lstDeferredObjectDestruction)
rprObject.first->ptrModule->DestroyObject(rprObject.second);
return 0;
}
@@ -1306,7 +1578,7 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr<CMo
auto* pObjectControl = ptrObject.GetInterface<sdv::IObjectControl>();
if (pObjectControl)
{
pObjectControl->Initialize(rssObjectConfig);
pObjectControl->Initialize(*ptrObjectEntry);
if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized)
{
// Shutdown the object (even if the initialization didn't work properly).
@@ -1315,8 +1587,8 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr<CMo
// Destroy the object
rptrModule->DestroyObject(ptrObject);
// Destroy the object again
DestroyObject(rssObjectName);
//// Destroy the object again
//DestroyObject2(rssObjectName, true);
return 0;
}
switch (GetAppControl().GetOperationState())
@@ -1350,14 +1622,7 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr<CMo
sdv::core::TObjectID CRepository::CreateObjectID()
{
static sdv::core::TObjectID tCurrent = 0;
if (!tCurrent)
{
std::srand(static_cast<unsigned int>(time(0)));
tCurrent = 0;
while (!tCurrent) tCurrent = std::rand();
}
return ++tCurrent;
return m_idgen.Generate();
}
std::vector<sdv::core::TObjectID> CRepository::GetDependingObjectInstancesByClass(const std::string& rssClass)
@@ -1378,6 +1643,7 @@ std::vector<sdv::core::TObjectID> CRepository::GetDependingObjectInstancesByClas
bool CRepositoryService::EnableRepositoryObjectControl()
{
return GetAppSettings().IsMainApplication() ||
GetAppSettings().IsMaintenanceApplication() ||
GetAppSettings().IsStandaloneApplication() ||
GetAppSettings().IsEssentialApplication() ||
GetAppSettings().IsIsolatedApplication();

View File

@@ -27,6 +27,7 @@
#include "object_lifetime_control.h"
#include "iso_monitor.h"
#include "app_config_file.h"
#include "../../global/unique_id.h"
/**
* @brief repository service providing functionality to load modules, create objects and access exiting objects
@@ -34,7 +35,8 @@
class CRepository :
public sdv::IInterfaceAccess, public sdv::core::IObjectAccess, public sdv::core::IRepositoryUtilityCreate,
public sdv::core::IRepositoryMarshallCreate, public sdv::core::IRepositoryControl, public sdv::core::IRegisterForeignObject,
public sdv::core::IRepositoryInfo, public IObjectDestroyHandler, public sdv::core::ILinkCoreRepository
public sdv::core::IRepositoryInfo, public IObjectDestroyHandler, public sdv::core::ILinkCoreRepository,
public sdv::core::IObjectDependency
{
public:
/**
@@ -45,6 +47,7 @@ public:
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::core::IObjectAccess)
SDV_INTERFACE_ENTRY(sdv::core::IObjectDependency)
SDV_INTERFACE_ENTRY(sdv::core::IRepositoryMarshallCreate)
SDV_INTERFACE_ENTRY(sdv::core::IRepositoryUtilityCreate)
END_SDV_INTERFACE_MAP()
@@ -102,7 +105,6 @@ public:
*/
virtual sdv::IInterfaceAccess* CreateStubObject(/*in*/ sdv::interface_id id) override;
protected:
/**
* @brief Create an object and all its objects it depends on. Overload of Overload of
* sdv::core::IRepositoryControl::CreateObject.
@@ -127,7 +129,7 @@ protected:
*/
virtual sdv::core::TObjectID CreateObject(/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName,
/*in*/ const sdv::u8string& ssObjectConfig) override;
public:
/**
* @brief Creates an object from a previously loaded module. Provide the module ID to explicitly define what module to
* use during object creation. Overload of sdv::core::IRepositoryControl::CreateObjectFromModule.
@@ -145,7 +147,6 @@ public:
/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName,
/*in*/ const sdv::u8string& ssObjectConfig) override;
protected:
/**
* @brief Destroy a previously created object with the supplied name. Overload of sdv::core::IRepositoryControl::DestroyObject.
* @details For standalone and essential applications previously created system, device and service objects can be
@@ -156,7 +157,29 @@ protected:
*/
virtual bool DestroyObject(/*in*/ const sdv::u8string& ssObjectName) override;
public:
private:
/**
* @brief Object entry
* @details The object instance information. Objects that are running remotely are represented by their proxy. Objects can have
* multiple references by stubs.
*/
struct SObjectEntry : sdv::SObjectInfo
{
sdv::TInterfaceAccessPtr ptrObject; ///< Object interface (could be proxy).
std::shared_ptr<CModuleInst> ptrModule; ///< Module instance.
bool bControlled = false; ///< When set, the object is controlled.
bool bIsolated = false; ///< When set, the object is isolated and running in another process.
std::mutex mtxConnect; ///< Mutex used to wait for connection
std::condition_variable cvConnect; ///< Condition variable used to wait for connection
std::shared_ptr<CIsoMonitor> ptrIsoMon; ///< Object being monitored for shutdown.
std::set<std::string> setDependentObjects; ///< List of dependent objects (needed during shutdown).
};
/**
* @brief List for collected objects ready for destruction.
*/
using TDeferredObjectDestructionList = std::list<std::pair<std::shared_ptr<SObjectEntry>, sdv::IInterfaceAccess*>>;
/**
* @brief Create an object and all its objects it depends on. Internal function not accessible through the interface.
* @param[in] ssClassName The name of the object class to be created. For the main application, the class string could
@@ -166,12 +189,13 @@ public:
* the class name. Use the returned object ID to request the name of the object.
* @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl. Only
* valid for standalone, essential and isolated applications.
* @param[in] bIndirect Set when the function was called through a call from GetObject.
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
* the object ID of the existing object is returned.
*/
sdv::core::TObjectID CreateObject2(/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName,
/*in*/ const sdv::u8string& ssObjectConfig);
sdv::core::TObjectID CreateObject2(const sdv::u8string& ssClassName, const sdv::u8string& ssObjectName,
const sdv::u8string& ssObjectConfig, bool bIndirect);
/**
* @brief Destroy a previously created object with the supplied name. Internal function not accessible through the interface.
@@ -179,10 +203,14 @@ public:
* destroyed. For the main and isolated applications, only the complex service can be destroyed. For isolated
* applications a destruction of the object will end the application.
* @param[in] ssObjectName The name of the object to destroy.
* @param[in] bIndirect Set when the function was not call directly through DestroyObject.
* @param[in] rlstDeferredObjectDestruction Reference to the list of objects to be filled with objects to be destroyed.
* @return Returns whether the object destruction was successful.
*/
bool DestroyObject2(/*in*/ const sdv::u8string& ssObjectName);
bool DestroyObject2(const sdv::u8string& ssObjectName, bool bIndirect,
TDeferredObjectDestructionList& rlstDeferredObjectDestruction);
public:
/**
* @brief Register as foreign object and make it public to the system with the given name. Overload of
* sdv::core::IRegisterForeignObject::RegisterObject.
@@ -196,15 +224,17 @@ public:
/*in*/ const sdv::u8string& ssObjectName) override;
/**
* @brief Register the core repository.
* @brief Register the core repository. Overload of sdv::core::ILinkCoreRepository::LinkCoreRepository.
* @param[in] pCoreRepository Pointer to the proxy interface of the core repository.
* @return Returns a link ID to be used in the Unlink function. Or 0 on failure.
*/
virtual void LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository) override;
virtual sdv::core::TLinkID LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository) override;
/**
* @brief Unlink a previously linked core repository.
* @brief Unlink a previously linked core repository. Overload of sdv::core::ILinkCoreRepository::UnlinkCoreRepository.
* @param[in] tLinkID Link ID of the repository link to remove.
*/
virtual void UnlinkCoreRepository() override;
virtual void UnlinkCoreRepository(sdv::core::TLinkID tLinkID) override;
/**
* @brief Find the class information of an object with the supplied name. Overload of
@@ -256,6 +286,20 @@ public:
*/
void DestroyAllObjects(const std::vector<std::string>&rvecIgnoreObjects, bool bForce = false);
/**
* @brief Add a dependency for an object. Overload of sdv::core::IObjectDependency::AddObjectDependency.
* @param[in] ssObjectName Name of the object.
* @param[in] ssDependsOnObject Name of the object it depends on.
*/
void AddObjectDependency(/*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssDependsOnObject) override;
/**
* @brief Remove an object dependency. Overload of sdv::core::IObjectDependency::RemoveObjectDependency.
* @param[in] ssObjectName Name of the object.
* @param[in] ssDependsOnObject Name of the object it depends on.
*/
void RemoveObjectDependency(/*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssDependsOnObject) override;
/**
* @brief Reset the current config baseline.
*/
@@ -306,7 +350,7 @@ private:
* @brief Create a new unique object ID.
* @return The created object ID.
*/
static sdv::core::TObjectID CreateObjectID();
sdv::core::TObjectID CreateObjectID();
/**
* @brief Get a list of depending object instances of a specific class.
@@ -315,26 +359,6 @@ private:
*/
std::vector<sdv::core::TObjectID> GetDependingObjectInstancesByClass(const std::string& rssClass);
/**
* @brief Object entry
* @details The object instance information. Objects that are running remotely are represented by their proxy. Objects can have
* multiple references by stubs.
*/
struct SObjectEntry
{
sdv::core::TObjectID tObjectID = 0; ///< Object ID (local to this process).
sdv::SClassInfo sClassInfo; ///< Object class name.
std::string ssName; ///< Object name (can be zero with local objects).
std::string ssConfig; ///< Object configuration.
sdv::TInterfaceAccessPtr ptrObject; ///< Object interface (could be proxy).
std::shared_ptr<CModuleInst> ptrModule; ///< Module instance.
bool bControlled = false; ///< When set, the object is controlled.
bool bIsolated = false; ///< When set, the object is isolated and running in another process.
std::mutex mtxConnect; ///< Mutex used to wait for connection
std::condition_variable cvConnect; ///< Condition variable used to wait for connection
std::shared_ptr<CIsoMonitor> ptrIsoMon; ///< Object being monitored for shutdown.
};
using TObjectMap = std::map<sdv::core::TObjectID, std::shared_ptr<SObjectEntry>>;
using TOrderedObjectList = std::list<std::shared_ptr<SObjectEntry>>;
using TObjectIDList = std::list<std::shared_ptr<SObjectEntry>>;
@@ -350,11 +374,11 @@ private:
TIsolationMap m_mapIsolatedObjects; ///< Map with isolated objects.
TObjectMap m_mapObjects; ///< Map with all objects indexed by the object ID.
TConfigSet m_setConfigObjects; ///< Set with the objects for storing in the configuration.
sdv::TInterfaceAccessPtr m_ptrCoreRepoAccess; ///< Linked core repository access (proxy interface).
std::list<std::pair<sdv::core::TLinkID, sdv::TInterfaceAccessPtr>> m_lstCoreRepoAccess; ///< List to Linked core repositories.
bool m_bIsoObjectLoaded = false; ///< When set, the isolated object has loaded. Do not allow
///< another object of type complex service or utility to be
///< created.
///
CUniqueID<sdv::core::TObjectID> m_idgen; ///< Unique ID generator.
};
/**

View File

@@ -23,6 +23,7 @@
#include "logger_control.h"
#include "logger.h"
#include "app_config.h"
#include "permission_control.h"
/**
* @brief SDV core instance class containing containing the instances for the core services.
@@ -47,6 +48,7 @@ public:
SDV_INTERFACE_CHAIN_MEMBER(GetMemoryManager())
SDV_INTERFACE_CHAIN_MEMBER(GetRepository())
SDV_INTERFACE_CHAIN_MEMBER(GetLoggerControl())
SDV_INTERFACE_CHAIN_MEMBER(GetPermissionControl())
END_SDV_INTERFACE_MAP()
/**

View File

@@ -37,7 +37,8 @@ namespace toml_parser
TTokenListIterator it = m_lstTokens.begin();
bool bCommaAvailable = false;
bool bPrintableCharsAvailable = false;
bool bLastTokenIsComment = false;
bool bLastTokenIsCommentOrForceNewline = false;
bool bNewlineAllowed = rContext.NewlineAllowed() || (eMode == EComposeMode::compose_behind && rContext.FinalNewline());
while (it != m_lstTokens.end() && it->Category() != ETokenCategory::token_comment)
{
// Add comma only when needed.
@@ -53,12 +54,12 @@ namespace toml_parser
bCommaAvailable = true;
break;
case ETokenCategory::token_comment:
bSkip = !rContext.CommentAndNewlineAllowed();
if (!bSkip) bLastTokenIsComment = true;
bSkip = !bNewlineAllowed;
if (!bSkip) bLastTokenIsCommentOrForceNewline = true;
break;
case ETokenCategory::token_syntax_new_line:
bSkip = !rContext.NewlineAllowed();
if (!bSkip) bLastTokenIsComment = false;
bSkip = !bNewlineAllowed;
if (!bSkip) bLastTokenIsCommentOrForceNewline = false;
break;
default:
break;
@@ -107,11 +108,11 @@ namespace toml_parser
break;
case ETokenCategory::token_comment:
bSkip = !rContext.CommentAndNewlineAllowed();
if (!bSkip) bLastTokenIsComment = true;
if (!bSkip) bLastTokenIsCommentOrForceNewline = true;
break;
case ETokenCategory::token_syntax_new_line:
bSkip = !rContext.NewlineAllowed();
if (!bSkip) bLastTokenIsComment = false;
bSkip = !bNewlineAllowed;
if (!bSkip) bLastTokenIsCommentOrForceNewline = false;
break;
default:
break;
@@ -210,11 +211,11 @@ namespace toml_parser
break;
case ETokenCategory::token_comment:
bSkip = !rContext.CommentAndNewlineAllowed();
if (!bSkip) bLastTokenIsComment = true;
if (!bSkip) bLastTokenIsCommentOrForceNewline = true;
break;
case ETokenCategory::token_syntax_new_line:
bSkip = !rContext.NewlineAllowed();
if (!bSkip) bLastTokenIsComment = false;
bSkip = !bNewlineAllowed;
if (!bSkip) bLastTokenIsCommentOrForceNewline = false;
break;
default:
break;
@@ -239,7 +240,7 @@ namespace toml_parser
}
// Default newline needed
if (rContext.FinalNewline() && ((m_lstTokens.empty() && m_ssComment.empty()) || bLastTokenIsComment))
if (rContext.FinalNewline() && ((m_lstTokens.empty() && m_ssComment.empty()) || bLastTokenIsCommentOrForceNewline))
sstream << std::endl;
break;
case EComposeMode::compose_before:

View File

@@ -16,6 +16,8 @@
#include "exception.h"
#include <sstream>
#include <limits>
#include <support/toml.h>
#include "parser_toml.h"
namespace toml_parser
{
@@ -560,4 +562,21 @@ namespace toml_parser
return sstreamQuotedText.str();
}
}
bool CompareEqual(const std::string& rssToml1, const std::string& rssToml2)
{
try
{
CParser parser1(rssToml1);
CParser parser2(rssToml2);
sdv::toml::CNodeCollection collection1(&parser1.Root());
sdv::toml::CNodeCollection collection2(&parser2.Root());
return sdv::toml::internal::CompareNodes(collection1, collection2) == sdv::toml::ECompareResult::compare_identical;
}
catch (const toml_parser::XTOMLParseException&)
{
return false;
}
}
} // namespace toml_parser

View File

@@ -108,6 +108,16 @@ namespace toml_parser
* @return The quoted key (if applicable); otherwise the key without quotes.
*/
std::string QuoteText(const std::string& rssText, EQuoteRequest eQuoteRequest = EQuoteRequest::smart_text);
/**
* @brief Compare the content of TOML string with the content of another TOML string. Use internal TOML parser for the
* comparison.
* @param[in] rssToml1 Reference to the first TOML string to compare with the second TOML string.
* @param[in] rssToml2 Reference to the second TOML string to compare with the first TOML string.
* @return The comparison result.
*/
bool CompareEqual(const std::string& rssToml1, const std::string& rssToml2);
} // namespace toml_parser
#endif // !defined MISCELLANEOUS_H

View File

@@ -0,0 +1,145 @@
/********************************************************************************
* 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:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "parser_node_indexer.h"
#include <interfaces/toml.h>
#include <iostream>
namespace toml_parser
{
CNodeIndex::CNodeIndex(CIndexList& rIndexList, CIndexListIterator itPos) : m_ptrPos(std::make_shared<CIteratorWrapper>(rIndexList, itPos))
{}
CNodeIndex::~CNodeIndex()
{
m_ptrPos.reset();
}
CNodeIndex::CNodeIndex(const CNodeIndex& rIndex) : m_ptrPos(rIndex.m_ptrPos)
{}
CNodeIndex::CNodeIndex(CNodeIndex&& rIndex) : m_ptrPos(std::move(rIndex.m_ptrPos))
{}
CNodeIndex& CNodeIndex::operator=(const CNodeIndex& rIndex)
{
m_ptrPos = rIndex.m_ptrPos;
return *this;
}
CNodeIndex& CNodeIndex::operator=(CNodeIndex&& rIndex)
{
m_ptrPos = std::move(rIndex.m_ptrPos);
return *this;
}
bool CNodeIndex::operator==(const CNodeIndex& rIndex) const
{
return rIndex.m_ptrPos == m_ptrPos;
}
bool CNodeIndex::operator!=(const CNodeIndex& rIndex) const
{
return !operator==(rIndex);
}
bool CNodeIndex::operator<(const CNodeIndex& rIndex) const
{
if (!m_ptrPos) return false;
if (!rIndex.m_ptrPos) return true;
return m_ptrPos->Index() < rIndex.m_ptrPos->Index();
}
bool CNodeIndex::operator<=(const CNodeIndex& rIndex) const
{
if (!m_ptrPos) return false;
if (!rIndex.m_ptrPos) return true;
return rIndex.m_ptrPos == m_ptrPos || m_ptrPos->Index() < rIndex.m_ptrPos->Index();
}
bool CNodeIndex::operator>(const CNodeIndex& rIndex) const
{
if (!m_ptrPos) return true;
if (!rIndex.m_ptrPos) return false;
return m_ptrPos->Index() > rIndex.m_ptrPos->Index();
}
bool CNodeIndex::operator>=(const CNodeIndex& rIndex) const
{
if (!m_ptrPos) return true;
if (!rIndex.m_ptrPos) return false;
return rIndex.m_ptrPos == m_ptrPos || m_ptrPos->Index() > rIndex.m_ptrPos->Index();
}
CNodeIndex::operator bool() const
{
return m_ptrPos ? true : false;
}
void CNodeIndex::MoveBefore(const CNodeIndex& rIndex)
{
if (m_ptrPos && rIndex.m_ptrPos)
m_ptrPos->MoveBeforeIndex(*rIndex.m_ptrPos);
}
uint32_t CNodeIndex::Index() const
{
if (!m_ptrPos) return sdv::toml::npos;
return m_ptrPos->Index();
}
CNodeIndex::CIteratorWrapper::CIteratorWrapper(CIndexList& rIndexList, CIndexListIterator itPos) :
m_rIndexList(rIndexList), m_itPos(itPos)
{}
CNodeIndex::CIteratorWrapper ::~CIteratorWrapper()
{
m_rIndexList.erase(m_itPos);
}
uint32_t CNodeIndex::CIteratorWrapper::Index() const
{
return static_cast<uint32_t>(std::distance(m_rIndexList.cbegin(), m_itPos));
}
void CNodeIndex::CIteratorWrapper::MoveBeforeIndex(const CIteratorWrapper& ritTarget)
{
m_rIndexList.splice(ritTarget.m_itPos, m_rIndexList, m_itPos);
}
// Global index list
CIndexList CNodeIndexer::m_lstIndexList;
CNodeIndexer::CNodeIndexer()
{}
CNodeIndex CNodeIndexer::CreateIndex()
{
auto itPos = m_lstIndexList.insert(m_lstIndexList.end(), SNodeIndexElement());
CNodeIndex index(m_lstIndexList, itPos);
return index;
}
CNodeIndex CNodeIndexer::CreateIndex(const CNodeIndex& rInsertBefore)
{
CNodeIndex node_index = CreateIndex();
node_index.MoveBefore(rInsertBefore);
return node_index;
}
size_t CNodeIndexer::Count()
{
return m_lstIndexList.size();
}
} // namespace toml_parser

View File

@@ -0,0 +1,227 @@
/********************************************************************************
* 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:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef PARSER_NODE_INDEXER_H
#define PARSER_NODE_INDEXER_H
#include <list>
#include <memory>
#include <cstdint>
namespace toml_parser
{
class CNodeIndex;
class CNodeIndexer;
/// Placeholder index element
struct SNodeIndexElement {};
/// Index list
using CIndexList = std::list<SNodeIndexElement>;
/// Const iterator to an element in the index list
using CIndexListIterator = CIndexList::const_iterator;
/**
* @brief Node index object used to manage the node order.
*/
class CNodeIndex
{
friend CNodeIndexer; ///< CNodeIndex uses friend access to create the node index and assign the iterator holding the
///< the position within the index list.
private:
/**
* @brief Constructor of the node index object. Use the CNodeIndexer::CreateIndex function to create the object.
* @param[in] rIndexList Reference to the index list.
* @param[in] itPos Iterator to the position in the index list.
*/
CNodeIndex(CIndexList& rIndexList, CIndexListIterator itPos);
public:
/**
* @brief Destructor
*/
virtual ~CNodeIndex();
/**
* @brief Copy constructor of the node index object.
* @param[in] rIndex Reference to the index object to copy from.
*/
CNodeIndex(const CNodeIndex& rIndex);
/**
* @brief Move constructor of the node index object.
* @param[in] rIndex Reference to the index object to move from.
*/
CNodeIndex(CNodeIndex&& rIndex);
/**
* @brief Copy assignment operator of the node index object.
* @param[in] rIndex Reference to the index object to copy from.
* @return Returns a reference to this object.
*/
CNodeIndex& operator=(const CNodeIndex& rIndex);
/**
* @brief Move assignment operator of the node index object.
* @param[in] rIndex Reference to the index object to move from.
* @return Returns a reference to this object.
*/
CNodeIndex& operator=(CNodeIndex&& rIndex);
/**
* @brief Compare this node index object to the supplied index object and return whether the position of this object is
* identical to the position of the supplied object.
* @param[in] rIndex Reference to the node index object to compare with.
* @return Returns whether the provided index is identical to this index.
*/
bool operator==(const CNodeIndex& rIndex) const;
/**
* @brief Compare this node index object to the supplied index object and return whether the position of this object is
* not identical to the position of the supplied object.
* @param[in] rIndex Reference to the node index object to compare with.
* @return Returns whether the provided index is different than this index.
*/
bool operator!=(const CNodeIndex& rIndex) const;
/**
* @brief Compare this node index object to the supplied index object and return whether the position of this object is
* smaller than the position of the supplied object.
* @param[in] rIndex Reference to the node index object to compare with.
* @return Returns whether the provided index is smaller than this index.
*/
bool operator<(const CNodeIndex& rIndex) const;
/**
* @brief Compare this node index object to the supplied index object and return whether the position of this object is
* smaller than or equal to the position of the supplied object.
* @param[in] rIndex Reference to the node index object to compare with.
* @return Returns whether the provided index is smaller than or equal to this index.
*/
bool operator<=(const CNodeIndex& rIndex) const;
/**
* @brief Compare this node index object to the supplied index object and return whether the position of this object is
* larger than the position of the supplied object.
* @param[in] rIndex Reference to the node index object to compare with.
* @return Returns whether the provided index is larger than this index.
*/
bool operator>(const CNodeIndex& rIndex) const;
/**
* @brief Compare this node index object to the supplied index object and return whether the position of this object is
* larger than or equal to the position of the supplied object.
* @param[in] rIndex Reference to the node index object to compare with.
* @return Returns whether the provided index is larger than or equal to this index.
*/
bool operator>=(const CNodeIndex& rIndex) const;
/**
* @brief Check for validity.
* @return Returns whether the index object contains a valid index.
*/
operator bool() const;
/**
* @brief Move the node index object before the index object provided as an argument.
* @param[in] rIndex Reference to the node index object to move the object before.
*/
void MoveBefore(const CNodeIndex& rIndex);
/**
* @brief Get the index in the list. Returns sdv::toml::npos if the index is not in the list any more.
* @remarks This index os calculated dynamically and can change if indices are added, removed or swapped.
* @return The current index in the list.
*/
uint32_t Index() const;
private:
/**
* @brief Iterator wrapper object with lifetime management.
* @remarks The wrapper keeps track of the shared index list as well. It is possible that the parser is deleted before the
* nodes are deleted, causing an issue when accessing the index list.
*/
struct CIteratorWrapper
{
/**
* @brief Constructor
* @param[in] rIndexList Reference to the index list.
* @param[in] itPos Position of this index in the index list.
*/
CIteratorWrapper(CIndexList& rIndexList, CIndexListIterator itPos);
/**
* @brief Destructor removing the index object from the index list.
*/
~CIteratorWrapper();
/**
* @brief Get the index in the list.
* @remarks This index os calculated dynamically and can change if indices are added, removed or swapped.
* @return The current index in the list.
*/
uint32_t Index() const;
/**
* @brief Move the index object before the supplied index object.
* @param[in] ritTarget Reference to the index object to move the object before.
*/
void MoveBeforeIndex(const CIteratorWrapper& ritTarget);
private:
CIndexList& m_rIndexList; ///< Reference to the index list.
CIndexListIterator m_itPos; ///< Iterator in the list
};
std::shared_ptr<CIteratorWrapper> m_ptrPos; ///< Position in the index list
};
/**
* @brief Node indexer object managing the node index objects allowing determining and changing the order of nodes.
*/
class CNodeIndexer
{
friend CNodeIndex; ///< CNodeIndex has friend relationship allowing access to specific indexing functions using the
///< index iterator from the index list.
public:
/**
* @brief Constructor
*/
CNodeIndexer();
/**
* @brief Create a node index object.
* @return The new node index object.
*/
CNodeIndex CreateIndex();
/**
* @brief Create a new node index object and insert this object before the supplied object.
* @attention There is no protection for supplying an invalid iterator or an iterator from a different indexer.
* @param[in] rInsertBefore Insert the index object before the supplied iterator position.
* @return The new node index object.
*/
CNodeIndex CreateIndex(const CNodeIndex& rInsertBefore);
/**
* @brief Return the amount of indices allocated.
* @return The amount of indices allocated in the index list.
*/
static size_t Count();
private:
static CIndexList m_lstIndexList; ///< List of all node indices. This list is global to allow nodes from one
///< parser to be inserted into the list of another parser.
};
} // namespace toml_parser
#endif // !defined PARSER_NODE_INDEXER_H

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@
#include <support/interface_ptr.h>
#include "miscellaneous.h"
#include "code_snippet.h"
#include "parser_node_indexer.h"
/// The TOML parser namespace
namespace toml_parser
@@ -72,11 +73,12 @@ namespace toml_parser
void InitTopMostNode(const std::shared_ptr<const CNode>& rptrNode);
/**
* @brief Check whether the provided node is a parent of the top most node.
* @param[in] rptrNode Reference to the node to use for the checking.
* @return Returns true if the node is a parent of the top most node, false otherwise.
* @brief A node is part of the view if either it is an inline node, or it is a standard node and the current parent is the
* root view.
* @param[in] rptrNode Reference to the smart pointer to the node to check for.
* @return Returns whether the node is part of the view.
*/
bool PartOfExcludedParents(const std::shared_ptr<const CNode>& rptrNode) const;
bool IsPartOfView(const std::shared_ptr<const CNode>& rptrNode) const;
/**
* @brief Create a copy of the context class with a new key context.
@@ -289,10 +291,42 @@ namespace toml_parser
END_SDV_INTERFACE_MAP()
/**
* @{
* @brief Get a reference to the TOML parser that generated this node.
* @return Reference to the TOML parse.
*/
CParser& Parser();
const CParser& Parser() const;
/**
* @}
*/
/**
* @{
* @brief Return the index object of the node.
* @return Reference to the node index object.
*/
const CNodeIndex& NodeIndex() const;
CNodeIndex& NodeIndex();
/**
* @}
*/
/**
* @brief Compare the index position of this node with the position of another node and return whether this node occurs
* before the other node.
* @param[in] rNode Reference to the node to compare the position with.
* @return Returns whether this node occurs before the other node.
*/
bool operator<(const CNode& rNode) const;
/**
* @brief Compare the index position of this node with the position of another node and return whether this node occurs
* before the other node.
* @param[in] rptrNode Reference to the node to compare the position with.
* @return Returns whether this node occurs before the other node.
*/
bool operator<(const std::shared_ptr<CNode>& rptrNode) const;
/**
* @brief Get the node name (no conversion to a literal or quoted key is made). Overload of sdv::toml::INodeInfo::GetName.
@@ -328,9 +362,8 @@ namespace toml_parser
virtual sdv::any_t GetValue() const override;
/**
* @brief Get the index of this node within the view collection (either the assigned view or the parent). Overload of
* sdv::toml::INodeInfo::GetIndex.
* @return The index of the node within the view collection node or npos when no parent is available.
* @brief Get the index of this node within the parent collection. Overload of sdv::toml::INodeInfo::GetIndex.
* @return The index of the node within the parent collection node or npos when no parent is available.
*/
virtual uint32_t GetIndex() const override;
@@ -370,10 +403,10 @@ namespace toml_parser
virtual sdv::u8string GetComment(sdv::toml::INodeInfo::ECommentType eType) override;
/**
* @brief Format the node automatically. This will remove the whitespace between the elements within the node. Comments
* will not be changed. Overload of sdv::toml::INodeInfo::AutomaticFormat.
* @brief Format the node automatically, remove redundant whitespace. Overload of sdv::toml::INodeInfo::AutomaticFormat.
* @param[in] bRemoveComments When set, the comments are removed from the node.
*/
virtual void AutomaticFormat() override;
virtual void AutomaticFormat(/*in*/ bool bRemoveComments) override;
/**
* @brief Is the node inline? Overload of sdv::toml::INodeInfo::IsInline.
@@ -457,10 +490,11 @@ namespace toml_parser
std::shared_ptr<const TNodeType> Cast() const;
/**
* @brief Set the parent node.
* @brief Reassign the parent node and if necessary the parser reference.
* @details This function allows shifting nodes from one parser to another.
* @param[in] rptrParent Reference to the node to assign to this node as a parent.
*/
void SetParentPtr(const std::shared_ptr<CNodeCollection>& rptrParent);
virtual void SetParentPtr(const std::shared_ptr<CNodeCollection>& rptrParent);
/**
* @brief Gets the parent node pointer.
@@ -475,29 +509,6 @@ namespace toml_parser
*/
std::string GetParentPath() const;
/**
* @brief Set the view definition node. The view definition node is a parent or grand parent that presents the node when
* generating TOML code. When not set, the parent node is taking over this role.
* @param[in] rptrView Reference to the node to assign to this node as a parent or grand parent.
*/
void SetViewPtr(const std::shared_ptr<CNodeCollection>& rptrView);
/**
* @brief Gets the view definition node pointer.
* @return Returns the store view definition node pointer or an empty pointer when no view was assigned.
*/
std::shared_ptr<CNodeCollection> GetViewPtr() const;
/**
* @brief Checks whether the node is part of the view.
* @details The node is part of the view if the supplied pointer is identical to the view definition pointer, when the view
* definition pointer is not part of a parent of the topmost node. In all other cases, the node is part of the view.
* @param[in] rContext Reference to the context class to use during TOML code generation.
* @param[in] rptrNode Reference to the node to check whether it registered for a view.
* @return Returns whether this node is part of the view with the supplied pointer.
*/
bool IsPartOfView(const CGenContext& rContext, const std::shared_ptr<const CNodeCollection>& rptrNode) const;
/**
* @brief Accesses a node by its key in the parse tree.
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name.
@@ -507,7 +518,7 @@ namespace toml_parser
* @attention Array indexing starts with 0!
* @attention For an array, when no indexing is supplied, the latest entry will be returned.
* @param[in] rssPath The path of the node to searched for.
* @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found.
* @return Returns a shared pointer to the wanted node if it was found or a node with invalid content if it was not found.
*/
virtual std::shared_ptr<CNode> Direct(const std::string& rssPath) const = 0;
@@ -577,13 +588,20 @@ namespace toml_parser
*/
std::string GetCustomPath(const std::string& rssPrefixKey, const std::string& rssContext) const;
/**
* @brief When the parent changes (e.g. when moving items from one parser to the other), the items and all its sub-items
* need to reasign the parser.
* @param[in] rParser Reference to the parser to assign.
*/
virtual void ReassignParser(CParser& rParser);
private:
std::weak_ptr<CNodeCollection> m_ptrParent; ///< Weak pointer to the parent node (if existing).
std::weak_ptr<CNodeCollection> m_ptrView; ///< Weak pointer to the view node (if existing and explicitly set).
std::string m_ssName; ///< Name of the node.
std::string m_ssRawName; ///< Raw name of the node.
bool m_bDeleted = false; ///< Enabled when the node was marked for deletion.
CParser& m_rParser; ///< Reference to the TOML parser.
CNodeIndex m_index; ///< Node index object holding the overall node position.
std::weak_ptr<CNodeCollection> m_ptrParent; ///< Weak pointer to the parent node (if existing).
std::string m_ssName; ///< Name of the node.
std::string m_ssRawName; ///< Raw name of the node.
bool m_bDeleted = false; ///< Enabled when the node was marked for deletion.
std::reference_wrapper<CParser> m_refParser; ///< Reference to the TOML parser.
std::vector<std::map<std::string, CCodeSnippet>> m_vecCodeSnippets; ///< Vector with comments/code snippets.
public:
@@ -596,11 +614,16 @@ namespace toml_parser
/**
* @brief With some node collections it is possible to switch between inline and normal.
* @remarks Additional node composition information will be removed and the order within the parent node might be changed.
* @remarks When made inline, all child nodes must be made inline as well. When made standard, only this node is made
* standard.
* @remarks Making this node a standard node, this is only possible when the parent is not an inline mode.
* @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal.
* @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables
* and table-arrays can be defined as standard). Making a node inline is always including the children.
* @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is
* always successful). When returning false, the switching might not be supported for this type.
*/
virtual bool Inline(bool bInline) = 0;
virtual bool Inline(bool bInline, bool bIncludeChildren = true) = 0;
/**
* @brief Checks whether the table was explicitly defined.
@@ -654,10 +677,12 @@ namespace toml_parser
* @brief With some node collections it is possible to switch between inline and normal. Overload of CNode::Inline.
* @remarks Additional node composition information will be removed and the order within the parent node might be changed.
* @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal.
* @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables
* and table-arrays can be defined as standard). Making a node inline is always including the children.
* @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is
* always successful). When returning false, the switching might not be supported for this type.
*/
virtual bool Inline(bool bInline) override;
virtual bool Inline(bool bInline, bool bIncludeChildren = true) override;
/**
* @brief Accesses a node by its key in the parse tree. Overload of CNode::Direct.
@@ -668,7 +693,7 @@ namespace toml_parser
* @attention Array indexing starts with 0!
* @attention For an array, when no indexing is supplied, the latest entry will be returned.
* @param[in] rssPath The path of the node to searched for.
* @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found.
* @return Returns a shared pointer to the wanted node if it was found or a node with invalid content if it was not found.
*/
virtual std::shared_ptr<CNode> Direct(const std::string& rssPath) const override;
@@ -943,10 +968,10 @@ namespace toml_parser
END_SDV_INTERFACE_MAP()
/**
* @brief Format the node automatically. This will remove the whitespace between the elements within the node. Comments
* will not be changed. Overload of sdv::toml::INodeInfo::AutomaticFormat.
* @brief Format the node automatically, remove redundant whitespace. Overload of sdv::toml::INodeInfo::AutomaticFormat.
* @param[in] bRemoveComments When set, the comments are removed from the node.
*/
virtual void AutomaticFormat() override;
virtual void AutomaticFormat(/*in*/ bool bRemoveComments) override;
/**
* @brief Returns the amount of nodes. Overload of sdv::toml::INodeCollection::GetCount.
@@ -966,7 +991,28 @@ namespace toml_parser
* @param[in] uiIndex Index of the node to get.
* @return Smart pointer to the node object.
*/
std::shared_ptr<CNode> Get(uint32_t uiIndex) const;
virtual std::shared_ptr<CNode> Get(uint32_t uiIndex) const;
/**
* @brief After every insert, deletion and shift, the node order of this and all sub- tables need to be rebuild.
* @param[in] bForce Force rebuild, even if locked.
*/
virtual void RebuildNodeOrder(bool bForce);
/**
* @brief After every insert, deletion and shift, the node order needs to be rebuild.
* @details Adding is done iteratively through the node list. In case the top level flag is set, all nodes are added that
* are marked explicit or if a node is implicit, the explicit sub-nodes are added. In case this is root view, all sub-nodes
* which are standard tables or standard table arrays are added. This flattens the hierarchy. When the root view is not set,
* only the tables and table arrays are added that are a direct child of the node collection. At the end of building the
* root view, the nodes are sorted using their index. Furthermore, inline nodes are moved to the beginning of the vector and
* standard nodes to the end.
* @param[in, out] rvecNodes Reference to the vector being filled with the nodes for the view.
* @param[in] bRootView Set when the count is top level.
* @param[in] bTopLevel Set when this is the top level node for adding sub nodes.
*/
virtual void FillNodeOrderVector(std::vector<std::shared_ptr<CNode>>& rvecNodes, bool bRootView = true,
bool bTopLevel = true);
/**
* @brief Accesses a node by its key in the parse tree. Overload of CNode::Direct.
@@ -977,7 +1023,7 @@ namespace toml_parser
* @attention Array indexing starts with 0!
* @attention For an array, when no indexing is supplied, the latest entry will be returned.
* @param[in] rssPath The path of the node to searched for.
* @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found.
* @return Returns a shared pointer to the wanted node if it was found or a node with invalid content if it was not found.
*/
virtual std::shared_ptr<CNode> Direct(const std::string& rssPath) const override;
@@ -993,85 +1039,124 @@ namespace toml_parser
*/
virtual sdv::IInterfaceAccess* GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const override;
/**
* @brief Get or create the parent nodes automotatically from the path.
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name.
* E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and
* traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'.
* These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'.
* @attention Array indexing starts with 0!
* @attention For an array, when no indexing or a too large index number is supplied, a new entry will be created and
* returned.
* @param[in] rssPath The path of the node to searched for.
* @param[in] bInsertTableArray Since a table array consists of an array and a table, this is different than the other
* insertions that only insert one element. Some special treatment is needed at certain points.
* @return Returns a pair with the shared pointer to the parent node and the leftover name. If the creation could not be
* done, a NULL pointer is returned.
*/
virtual std::pair<std::shared_ptr<CNodeCollection>, std::string> SmartParentCreate(const std::string& rssPath,
bool bInsertTableArray = false);
/**
* @brief Insert a value into the collection at the location before the supplied index. Overload of
* sdv::toml::INodeCollectionInsert::InsertValue.
* @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the
* collection count to insert the node at the end of the collection. Value nodes cannot be inserted behind external
* tables and table arrays. If the index is referencing a position behind an external table or a table array, the index
* is automatically corrected.
* @param[in] ssName Name of the node to insert. Will be ignored for an array collection. The name must adhere to the
* key names defined by the TOML specification. Defining the key multiple times is not allowed. Quotation of key names
* is done automatically; the parser decides itself whether the key is bare-key, a literal key or a quoted key.
* @param[in] anyValue The value of the node, being either an integer, floating point number, virtual bool value or a string.
* @remarks In TOML, inline nodes are located before standard nodes. Since values are presented as inline node, they
* will be inserted before any standard node (table or table array if defined as standard node).
* @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between
* square brackets. Can be empty, causing the node to be inserted at the end.
* @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if
* not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty
* string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML
* specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the
* parser decides itself whether the key is bare-key, a literal key or a quoted key.
* @param[in] anyValue The value of the node, being either an integer, floating point number, virtual bool value or a
* string.
* Conversion is automatically done to int64, double float, bool or u8string.
* @return On success the interface to the newly inserted node is returned or NULL otherwise.
*/
virtual sdv::IInterfaceAccess* InsertValue(uint32_t uiIndex, const sdv::u8string& ssName, sdv::any_t anyValue) override;
virtual sdv::IInterfaceAccess* InsertValue(/*in*/ const sdv::u8string& ssInsertBefore, /*in*/ const sdv::u8string& ssName,
/*in*/ sdv::any_t anyValue) override;
/**
* @brief Insert an array into the collection at the location before the supplied index. Overload of
* sdv::toml::INodeCollectionInsert::InsertArray.
* @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the
* collection count to insert the node at the end of the collection. Array nodes cannot be inserted behind external
* tables and table arrays. If the index is referencing a position behind an external table or a table array, the index
* is automatically corrected.
* @param[in] ssName Name of the array node to insert. Will be ignored if the current node is also an array collection.
* The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not
* allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a
* literal key or a quoted key.
* @remarks In TOML, inline nodes are located before standard nodes. Since arrays are presented as inline node, they
* will be inserted before any standard node (table or table array if defined as standard node).
* @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between
* square brackets. Can be empty, causing the node to be inserted at the end.
* @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if
* not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty
* string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML
* specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the
* parser decides itself whether the key is bare-key, a literal key or a quoted key.
* @return On success the interface to the newly inserted node is returned or NULL otherwise.
*/
virtual sdv::IInterfaceAccess* InsertArray(uint32_t uiIndex, const sdv::u8string& ssName) override;
virtual sdv::IInterfaceAccess* InsertArray(/*in*/ const sdv::u8string& ssInsertBefore,
/*in*/ const sdv::u8string& ssName) override;
/**
* @brief Insert a table into the collection at the location before the supplied index. Overload of
* sdv::toml::INodeCollectionInsert::InsertTable.
* @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the
* collection count to insert the node at the end of the collection. Table nodes cannot be inserted before value nodes
* or arrays. If the index is referencing a position before a value node or an array, the index is automatically
* corrected.
* @param[in] ssName Name of the table node to insert. Will be ignored if the current node is an array collection.
* The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not
* allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a
* literal key or a quoted key.
* @remarks In TOML, inline nodes are located before standard nodes. Tables can be inserted as inline node, in which
* case they will be inserted before any standard node (table or table array if defined as standard node).
* @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between
* square brackets. Can be empty, causing the node to be inserted at the end.
* @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if
* not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty
* string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML
* specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the
* parser decides itself whether the key is bare-key, a literal key or a quoted key.
* @param[in] ePreference The preferred form of the node to be inserted.
* @return On success the interface to the newly inserted node is returned or NULL otherwise.
*/
virtual sdv::IInterfaceAccess* InsertTable(uint32_t uiIndex, const sdv::u8string& ssName,
sdv::toml::INodeCollectionInsert::EInsertPreference ePreference) override;
virtual sdv::IInterfaceAccess* InsertTable(/*in*/ const sdv::u8string& ssInsertBefore, /*in*/ const sdv::u8string& ssName,
/*in*/ sdv::toml::EInsertPreference ePreference) override;
/**
* @brief Insert a table array into the collection at the location before the supplied index. Overload of
* sdv::toml::INodeCollectionInsert::InsertTableArray.
* @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the
* collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value
* nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically
* corrected.
* @param[in] ssName Name of the array node to insert. Will be ignored if the current node is also an array collection.
* The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not
* allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a
* literal key or a quoted key.
* @remarks In TOML, inline nodes are located before standard nodes. Table arrays can be inserted as inline node, in
* which case they will be inserted before any standard node (table or table array if defined as standard node).
* @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between
* square brackets. Can be empty, causing the node to be inserted at the end.
* @param[in] ssName Name of the node to insert. This name can contain parent nodes, which are automatically created if
* not existing. With arrays, since the value in TOML doesn't have a name, the name of the value must be an empty
* string and any names provided are considered parent nodes. The name must adhere to the key names defined by the TOML
* specification. Defining the key multiple times is not allowed. Quotation of key names is done automatically; the
* parser decides itself whether the key is bare-key, a literal key or a quoted key.
* @param[in] ePreference The preferred form of the node to be inserted.
* @return On success the interface to the newly inserted node is returned or NULL otherwise.
*/
virtual sdv::IInterfaceAccess* InsertTableArray(uint32_t uiIndex, const sdv::u8string& ssName,
sdv::toml::INodeCollectionInsert::EInsertPreference ePreference) override;
virtual sdv::IInterfaceAccess* InsertTableArray(/*in*/ const sdv::u8string& ssInsertBefore, /*in*/ const sdv::u8string& ssName,
/*in*/ sdv::toml::EInsertPreference ePreference) override;
/**
* @brief Insert a TOML string as a child of the current collection node. If the collection is a table, the TOML string
* should contain values and inline/external/array-table nodes with names. If the collection is an array, the TOML
* string should contain and inline table nodes without names. Overload of sdv::toml::INodeCollectionInsert::InsertTOML.
* @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the
* collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value
* nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically
* corrected.
* @remarks In TOML, inline nodes are located before standard nodes. Dependable on the nodes defined in the TOML they
* might be transferred to inline or they might be inserted at a different location.
* @param[in] ssInsertBefore Name of the node to insert the value before. In case of an array, can be an index between
* square brackets. Can be empty, causing the node to be inserted at the end.
* @param[in] ssTOML The TOML string to insert.
* @param[in] bRollbackOnPartly If only part of the nodes could be inserted, no node will be inserted.
* @return The result of the insertion.
*/
virtual sdv::toml::INodeCollectionInsert::EInsertResult InsertTOML(uint32_t uiIndex, const sdv::u8string& ssTOML,
bool bRollbackOnPartly) override;
virtual sdv::toml::INodeCollectionInsert::EInsertResult InsertTOML(/*in*/ const sdv::u8string& ssInsertBefore,
/*in*/ const sdv::u8string& ssTOML, /*in*/ bool bRollbackOnPartly) override;
/**
* @brief Insert a TOML string as a child of the current collection node. If the collection is a table, the TOML string
* should contain values and inline/external/array-table nodes with names. If the collection is an array, the TOML
* string should contain and inline table nodes without names.
* @param[in] rptrInsertBefore The node to insert the TOML nodes before (if possible). Can be NULL, causing the TOML nodes
* to be inserted at the end.
* @param[in] ssTOML The TOML string to insert.
* @param[in] bRollbackOnPartly If only part of the nodes could be inserted, no node will be inserted.
* @return A pair structure with the result of the insertion and a vector of all the inserted nodes.
*/
std::pair<sdv::toml::INodeCollectionInsert::EInsertResult, std::vector<std::shared_ptr<CNode>>> InsertTOML(
const std::shared_ptr<CNode>& rptrInsertBefore, const sdv::u8string& ssTOML, bool bRollbackOnPartly);
/**
* @brief Delete the current node. Overload of sdv::toml::INodeUpdate::DeleteNode.
@@ -1080,6 +1165,23 @@ namespace toml_parser
*/
virtual bool DeleteNode() override;
/**
* @brief The derived class from the node collection can be inline or not. Overload of CNode::Inline.
* @return Returns whether the node is an inline node.
*/
virtual bool Inline() const override;
/**
* @brief With some node collections it is possible to switch between inline and normal. Overload of CNode::Inline.
* @remarks Additional node composition information will be removed and the order within the parent node might be changed.
* @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal.
* @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables
* and table-arrays can be defined as standard). Making a node inline is always including the children.
* @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is
* always successful). When returning false, the switching might not be supported for this type.
*/
virtual bool Inline(bool bInline, bool bIncludeChildren = true) override;
/**
* @brief Can the node convert to an inline definition? Overload of sdv::toml::INodeCollectionConvert::CanMakeInline.
* @return Returns whether the conversion to inline is possible. Returns 'true' when the node is already inline.
@@ -1101,10 +1203,12 @@ namespace toml_parser
/**
* @brief Convert the node to a standard node. Overload of sdv::toml::INodeCollectionConvert::MakeStandard.
* @param[in] bIncludeChildren When set, applicable child nodes are made are converted to standard nodes as well (only
* tables and table-arrays can be defined as standard).
* @return Returns whether the conversion was successful. Returns 'true' when the node was already defined as standard
* node.
*/
virtual bool MakeStandard() override;
virtual bool MakeStandard(/*in*/ bool bIncludeChildren) override;
/**
* @brief Delete a node from the collection.
@@ -1114,28 +1218,6 @@ namespace toml_parser
*/
bool DeleteNode(const std::shared_ptr<CNode>& rptrNode);
/**
* @brief Insert the node into the view.
* @remarks The node must be a descendant (direct child or an indirect child) of the node.
* @details Insert the node into the vector (and remove it from any previous vector if still assigned). Inline nodes can be
* assigned to stadard and inline nodes. Standard nodes can only be assigned to standard nodes. In the vector, first the
* inline nodes and then the standard nodes are located. This means that an inline node will be placed before standard nodes
* and standard nodes behind inline nodes, regardless of the index provided.
* @param[in] uiIndex Location within the vector to insert the node. Could be sdv::toml::npos to insert as last node.
* @param[in] rptrNode Reference to the smart pointer to the node to set the view for. If the node is not a direct child
* node, the view pointer of the node will be set.
* @return Returns true when the insertion was successful, false when node (likely because the node to insert is a standard
* node, whereas this node is an inline node.
*/
bool InsertIntoView(uint32_t uiIndex, const std::shared_ptr<CNode>& rptrNode);
/**
* @brief Remove a node from a view.
* @param[in] rptrNode Reference to the smart pointer pointing to the node to remove.
* @return Returns whether the removal was successful.
*/
bool RemoveFromView(const std::shared_ptr<CNode>& rptrNode);
/**
* @brief Find the index belonging to the provided node.
* @param[in] rptrNode Reference to the smart pointer holding the node to return the index for.
@@ -1151,7 +1233,7 @@ namespace toml_parser
bool IsDescendant(const std::shared_ptr<CNode>& rptrNode) const;
/**
* @brief Generic inserting function for nodes.
* @brief Generic add function for nodes.
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name.
* E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and
* traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'.
@@ -1160,10 +1242,6 @@ namespace toml_parser
* @attention For an array, when no indexing is supplied, the latest entry will be returned.
* @remarks If the node to insert exists already, but is marked implicit, the node will be returned and made explicit. In
* all other cases the an error will occur that the node already exists.
* @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the
* collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value
* nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically
* corrected.
* @param[in] rrangeKeyPath Reference to the token range containing the path to the node to insert.
* @param[in] rtArgs Zero or more references to arguments passed to the constructor of the node classes being created by
* this function.
@@ -1171,7 +1249,7 @@ namespace toml_parser
* there the returned node is a table within the table array.
*/
template <typename TNodeType, typename... TArgs>
std::shared_ptr<CNode> Insert(uint32_t uiIndex, const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs);
std::shared_ptr<CNode> AddNodeFromRange(const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs);
/**
* @brief Combine the collection with the provided content (mathematical union).
@@ -1191,6 +1269,13 @@ namespace toml_parser
*/
virtual bool Reduce(const std::shared_ptr<CNodeCollection>& rptrCollection) = 0;
/**
* @brief When the parent changes (e.g. when moving items from one parser to the other), the items and all its sub-items
* need to reasign the parser. Overload of CNode::ReassignParser.
* @param[in] rParser Reference to the parser to assign.
*/
virtual void ReassignParser(CParser& rParser) override;
private:
/**
* @brief When set, the child nodes need grouping (values following each other, tables and table arrays at the end).
@@ -1228,8 +1313,6 @@ namespace toml_parser
// The child node uses the parent node pointer to indicate which node holds node.
// The child node uses the view node pointer to indicate which node displays the node content.
std::vector<std::shared_ptr<CNode>> m_vecNodeOrder; ///< Vector holding the child nodes (could contain grand children as
///< well).
std::list<std::shared_ptr<CNode>> m_lstNodes; ///< List holding the direct child nodes.
std::list<std::shared_ptr<CNode>> m_lstRecycleBin; ///< List holding the child elements that were deleted. This will
///< prevent destruction of the node class, which would otherwise
@@ -1283,6 +1366,26 @@ namespace toml_parser
*/
virtual sdv::toml::ENodeType GetType() const override;
/**
* @brief Returns the amount of nodes. Overload of sdv::toml::INodeCollection::GetCount.
* @return The amount of nodes.
*/
virtual uint32_t GetCount() const override;
/**
* @brief Get the node. Overload of CNodeCollection::Get.
* @param[in] uiIndex Index of the node to get.
* @return Smart pointer to the node object.
*/
virtual std::shared_ptr<CNode> Get(uint32_t uiIndex) const override;
/**
* @brief Delete the current node. Overload of sdv::toml::INodeUpdate::DeleteNode.
* @attention A successful deletion will cause all interfaces to the current node to become inoperable.
* @return Returns whether the deletion was successful.
*/
virtual bool DeleteNode() override;
/**
* @brief Create the TOML text based on the content using an optional prefix node. Overload of CNode::GenerateTOML.
* @param[in] rContext Reference to the context class to use during TOML code generation.
@@ -1308,10 +1411,12 @@ namespace toml_parser
* table doesn't have a name.
* @remarks Additional node composition information will be removed and the order within the parent node might be changed.
* @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal.
* @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables
* and table-arrays can be defined as standard). Making a node inline is always including the children.
* @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is
* always successful). When returning false, the switching might not be supported for this type.
*/
virtual bool Inline(bool bInline) override;
virtual bool Inline(bool bInline, bool bIncludeChildren = true) override;
/**
* @brief Checks whether the table was explicitly defined. Overload of CNodeCollection::ExplicitlyDefined.
@@ -1342,9 +1447,17 @@ namespace toml_parser
*/
virtual bool Reduce(const std::shared_ptr<CNodeCollection>& rptrCollection) override;
/**
* @brief After every insert, deletion and shift, the node order of this and all sub- tables need to be rebuild.
* @param[in] bForce Force rebuild, even if locked.
*/
virtual void RebuildNodeOrder(bool bForce) override;
private:
bool m_bDefinedExplicitly = true; ///< When set, the table is defined explicitly.
bool m_bInline = false; ///< Flag determining whether the table is inline or not.
bool m_bDefinedExplicitly = true; ///< When set, the table is defined explicitly.
bool m_bInline = false; ///< Flag determining whether the table is inline or not.
std::vector<std::shared_ptr<CNode>> m_vecNodeOrder; ///< Vector holding the child nodes (could contain grand children as
///< well).
};
/**
@@ -1426,10 +1539,28 @@ namespace toml_parser
* @attention Array element access indices starts with 0!
* @attention For an array element inserting, when no indexing is supplied, the latest entry will be returned.
* @param[in] rssPath Reference to the path of the node to searched for.
* @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found.
* @return Returns a shared pointer to the wanted node if it was found or a node with invalid content if it was not found.
*/
virtual std::shared_ptr<CNode> Direct(const std::string& rssPath) const override;
/**
* @brief Get or create the parent nodes automotatically from the path. Overload of CNodeCollection::SmartParentCreate.
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name.
* E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and
* traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'.
* These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'.
* @attention Array indexing starts with 0!
* @attention For an array, when no indexing or a too large index number is supplied, a new entry will be created and
* returned.
* @param[in] rssPath The path of the node to searched for.
* @param[in] bInsertTableArray Since a table array consists of an array and a table, this is different than the other
* insertions that only insert one element. Some special treatment is needed at certain points.
* @return Returns a pair with the shared pointer to the parent node and the leftover name. If the creation could not be
* done, a NULL pointer is returned.
*/
virtual std::pair<std::shared_ptr<CNodeCollection>, std::string> SmartParentCreate(const std::string& rssPath,
bool bInsertTableArray = false) override;
/**
* @brief Create the TOML text based on the content using an optional prefix node. Overload of CNode::GenerateTOML.
* @param[in] rContext Reference to the context class to use during TOML code generation.
@@ -1450,13 +1581,6 @@ namespace toml_parser
*/
bool TableArray() const;
/**
* @brief Can the node convert to a standard definition? Overload of sdv::toml::INodeCollectionConvert::CanMakeStandard.
* @return Returns whether the conversion to standard is possible. Returns 'true' when the node is already defined as
* standard node.
*/
virtual bool CanMakeStandard() const override;
/**
* @brief The derived class from the node collection can be inline or not. Overload of CNode::Inline.
* @return Returns whether the node is an inline node.
@@ -1472,10 +1596,19 @@ namespace toml_parser
* doesn't have a name that can be used to define the explicit table array.
* @remarks Additional node composition information will be removed and the order within the parent node might be changed.
* @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal.
* @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables
* and table-arrays can be defined as standard). Making a node inline is always including the children.
* @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is
* always successful). When returning false, the switching might not be supported for this type.
*/
virtual bool Inline(bool bInline) override;
virtual bool Inline(bool bInline, bool bIncludeChildren = true) override;
/**
* @brief Can the node convert to a standard definition? Overload of sdv::toml::INodeCollectionConvert::CanMakeStandard.
* @return Returns whether the conversion to standard is possible. Returns 'true' when the node is already defined as
* standard node.
*/
virtual bool CanMakeStandard() const override;
/**
* @brief Does the last child node need a comma following the node?
@@ -1534,7 +1667,7 @@ namespace toml_parser
* @brief Constructor
* @param[in] rparser Reference to the TOML parser.
*/
CRootTable(CParser& rparser) : CTable(rparser, "root", "", false)
CRootTable(CParser& rparser) : CTable(rparser, "root", "", false, true)
{}
/**
@@ -1550,20 +1683,25 @@ namespace toml_parser
*/
virtual bool Inline() const override
{
// The root node can never be inline.
return false;
}
/**
* @brief With some node collections it is possible to switch between inline and normal. Overload of
* CNodeCollection::Inline.
* @brief Switch between inline and explicit table definition. Overload of CNodeCollection::Inline.
* @attention It is not possible to switch to an explicit table definition if the table is part of an array, since the
* table doesn't have a name.
* @remarks Additional node composition information will be removed and the order within the parent node might be changed.
* @param[in] bInline When set, try to switch to inline. Otherwise try to switch to normal.
* @param[in] bIncludeChildren When set and bInline is not set, applicable child nodes are converted as well (only tables
* and table-arrays can be defined as standard). Making a node inline is always including the children.
* @return Returns whether the switch was successful. A switch to the same type (normal to normal or inline to inline is
* always successful). When returning false, the switching might not be supported for this type.
*/
virtual bool Inline(bool bInline) override
virtual bool Inline(bool bInline, bool bIncludeChildren = true) override
{
return bInline == false;
// Pass call to table implementation.
return CTable::Inline(bInline, bIncludeChildren);
}
};
@@ -1580,7 +1718,7 @@ namespace toml_parser
}
template <typename TNodeType, typename... TArgs>
inline std::shared_ptr<CNode> CNodeCollection::Insert(uint32_t uiIndex, const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs)
inline std::shared_ptr<CNode> CNodeCollection::AddNodeFromRange(const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs)
{
// Get the first part of the node
auto prKey = SplitNodeKey(rrangeKeyPath);
@@ -1613,9 +1751,8 @@ namespace toml_parser
"' exists already, but is not a table array.");
// Create the table.
ptrNode = ptrTableArray->Insert<CTable>(sdv::toml::npos, CTokenRange(prKey.first, prKey.first.get().Next()),
ptrNode = ptrTableArray->AddNodeFromRange<CTable>(CTokenRange(prKey.first, prKey.first.get().Next()),
false, true);
ptrNode->SetViewPtr(Cast<CNodeCollection>());
} else if (!Cast<CArray>() && itNode != m_lstNodes.end())
{
// If existing... this might be a duplicate if not explicitly defined before.
@@ -1644,7 +1781,7 @@ namespace toml_parser
// If the current node is implicit, take over the inline flag (this determines whether a sub-table definition is
// allowed or not).
if (!ExplicitlyDefined())
Inline(ptrNode->Inline());
Inline(ptrNode->Inline(), false);
}
}
else // Intermediate node
@@ -1663,6 +1800,9 @@ namespace toml_parser
auto prNextKey = SplitNodeKey(prKey.second);
if (prNextKey.first.get().Category() != ETokenCategory::token_integer)
{
// The table array should have an ordered vector. This is not the case yet. Rebuild the table array.
ptrNode->Cast<CTableArray>()->RebuildNodeOrder(true);
// Get the last table
if (!ptrNode->Cast<CTableArray>()->GetCount())
throw XTOMLParseException("The parent table array node '" + prKey.first.get().StringValue() +
@@ -1700,23 +1840,14 @@ namespace toml_parser
std::shared_ptr<CNodeCollection> ptrNodeCollection = ptrNode->Cast<CNodeCollection>();
if (!ptrNodeCollection)
throw XTOMLParseException("Parent node is not an array or table '" + ptrNode->GetPath(true) + "'.");
ptrNode = ptrNodeCollection->Insert<TNodeType>(sdv::toml::npos, prKey.second, rtArgs...);
ptrNode = ptrNodeCollection->AddNodeFromRange<TNodeType>(prKey.second, rtArgs...);
if (!ptrNode)
throw XTOMLParseException("Could not create the node '" + prKey.first.get().StringValue() + "'.");
}
// Insert the node at the requested location if this node is inline or this is the view of the node.
auto itPos = (static_cast<size_t>(uiIndex) >= m_vecNodeOrder.size()) ? m_vecNodeOrder.end() :
m_vecNodeOrder.begin() + static_cast<size_t>(uiIndex);
m_vecNodeOrder.insert(itPos, ptrNode);
if (ptrNode->GetParentPtr() != Cast<CNodeCollection>())
ptrNode->SetViewPtr(Cast<CNodeCollection>());
// Return the result
return ptrNode;
}
} // namespace toml_parser
#endif // !defined PARSER_NODE_TOML_H

View File

@@ -54,6 +54,9 @@ namespace toml_parser
try
{
// Lock the rebuild of the node order
auto lock = CreateRebuildLockObject();
// Run through all tokens of the lexer and process the tokens.
bool bEOF = false; // Explicit test, since the peek could return EOF, but the cursor might not be at the end yet.
while (!bEOF && !m_lexer.IsEnd())
@@ -95,7 +98,7 @@ namespace toml_parser
}
catch (const sdv::toml::XTOMLParseException& e)
{
std::cout << e.what() << '\n';
std::cerr << e.what() << std::endl;
throw;
}
@@ -115,6 +118,11 @@ namespace toml_parser
return m_lexer;
}
CNodeIndexer& CParser::Indexer()
{
return m_indexer;
}
const CNodeCollection& CParser::Root() const
{
auto ptrCollection = m_ptrRoot->Cast<CTable>();
@@ -139,6 +147,49 @@ namespace toml_parser
return m_ptrRoot->GenerateTOML(rssPrefixKey);
}
CParser::CLockRebuild::CLockRebuild(CParser& rParser) : m_rParser(rParser)
{
rParser.IncrRebuildLockCnt();
}
CParser::CLockRebuild::CLockRebuild(const CLockRebuild& rLockRebuild) : m_rParser(rLockRebuild.m_rParser)
{
m_rParser.IncrRebuildLockCnt();
}
CParser::CLockRebuild::CLockRebuild(CLockRebuild&& rLockRebuild) : m_rParser(rLockRebuild.m_rParser)
{
m_rParser.IncrRebuildLockCnt();
}
CParser::CLockRebuild::~CLockRebuild()
{
m_rParser.DecrRebuildLockCnt();
}
CParser::CLockRebuild CParser::CreateRebuildLockObject()
{
return CLockRebuild(*this);
}
bool CParser::RebuildLocked() const
{
return m_nRebuildLockCnt ? true : false;
}
void CParser::IncrRebuildLockCnt()
{
++m_nRebuildLockCnt;
}
void CParser::DecrRebuildLockCnt()
{
if (!m_nRebuildLockCnt)
return; // Should not occur
--m_nRebuildLockCnt;
if (!m_nRebuildLockCnt) Root().RebuildNodeOrder(false);
}
void CParser::ProcessTable(CNodeTokenRange& rNodeRange)
{
// Get the table path (table name preceded by parent tables separated with dots).
@@ -154,7 +205,7 @@ namespace toml_parser
m_lexer.SmartExtendNodeRange(rNodeRange);
// Add the table to the root
auto ptrTable = m_ptrRoot->Insert<CTable>(sdv::toml::npos, rangeKeyPath, false);
auto ptrTable = m_ptrRoot->AddNodeFromRange<CTable>(rangeKeyPath, false);
if (ptrTable)
{
m_ptrCurrentCollection = ptrTable->Cast<CTable>();
@@ -176,7 +227,7 @@ namespace toml_parser
m_lexer.SmartExtendNodeRange(rNodeRange);
// Add the table array to the root
auto ptrTableArray = m_ptrRoot->Insert<CTableArray>(sdv::toml::npos, rangeKeyPath);
auto ptrTableArray = m_ptrRoot->AddNodeFromRange<CTableArray>(rangeKeyPath);
if (ptrTableArray)
{
m_ptrCurrentCollection = ptrTableArray->Cast<CNodeCollection>();
@@ -226,46 +277,43 @@ namespace toml_parser
switch (rAssignmentValue.Category())
{
case ETokenCategory::token_boolean:
ptrNode = m_ptrCurrentCollection->Insert<CBooleanNode>(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.BooleanValue(),
ptrNode = m_ptrCurrentCollection->AddNodeFromRange<CBooleanNode>(rrangeKeyPath, rAssignmentValue.BooleanValue(),
rAssignmentValue.RawString());
break;
case ETokenCategory::token_integer:
ptrNode = m_ptrCurrentCollection->Insert<CIntegerNode>(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.IntegerValue(),
ptrNode = m_ptrCurrentCollection->AddNodeFromRange<CIntegerNode>(rrangeKeyPath, rAssignmentValue.IntegerValue(),
rAssignmentValue.RawString());
break;
case ETokenCategory::token_float:
ptrNode = m_ptrCurrentCollection->Insert<CFloatingPointNode>(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.FloatValue(),
ptrNode = m_ptrCurrentCollection->AddNodeFromRange<CFloatingPointNode>(rrangeKeyPath, rAssignmentValue.FloatValue(),
rAssignmentValue.RawString());
break;
case ETokenCategory::token_string:
switch (rAssignmentValue.StringType())
{
case ETokenStringType::literal_string:
ptrNode = m_ptrCurrentCollection->Insert<CStringNode>(sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(),
ptrNode = m_ptrCurrentCollection->AddNodeFromRange<CStringNode>(rrangeKeyPath, rAssignmentValue.StringValue(),
CStringNode::EQuotationType::literal_string, rAssignmentValue.RawString());
break;
case ETokenStringType::multi_line_literal:
ptrNode = m_ptrCurrentCollection->Insert<CStringNode>(
sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), CStringNode::EQuotationType::multi_line_literal,
rAssignmentValue.RawString());
ptrNode = m_ptrCurrentCollection->AddNodeFromRange<CStringNode>(rrangeKeyPath, rAssignmentValue.StringValue(),
CStringNode::EQuotationType::multi_line_literal, rAssignmentValue.RawString());
break;
case ETokenStringType::multi_line_quoted:
ptrNode = m_ptrCurrentCollection->Insert<CStringNode>(
sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), CStringNode::EQuotationType::multi_line_quoted,
rAssignmentValue.RawString());
ptrNode = m_ptrCurrentCollection->AddNodeFromRange<CStringNode>(rrangeKeyPath, rAssignmentValue.StringValue(),
CStringNode::EQuotationType::multi_line_quoted, rAssignmentValue.RawString());
break;
case ETokenStringType::quoted_string:
default:
ptrNode = m_ptrCurrentCollection->Insert<CStringNode>(
sdv::toml::npos, rrangeKeyPath, rAssignmentValue.StringValue(), CStringNode::EQuotationType::quoted_string,
rAssignmentValue.RawString());
ptrNode = m_ptrCurrentCollection->AddNodeFromRange<CStringNode>(rrangeKeyPath, rAssignmentValue.StringValue(),
CStringNode::EQuotationType::quoted_string, rAssignmentValue.RawString());
break;
}
break;
case ETokenCategory::token_syntax_array_open:
{
auto ptrCurrentCollectionStored = m_ptrCurrentCollection;
ptrNode = m_ptrCurrentCollection->Insert<CArray>(sdv::toml::npos, rrangeKeyPath);
ptrNode = m_ptrCurrentCollection->AddNodeFromRange<CArray>(rrangeKeyPath);
m_ptrCurrentCollection = ptrNode->Cast<CNodeCollection>();
m_stackEnvironment.push(EEnvironment::array);
ProcessArray(rNodeRange);
@@ -276,7 +324,7 @@ namespace toml_parser
case ETokenCategory::token_syntax_inline_table_open:
{
auto ptrCurrentCollectionStored = m_ptrCurrentCollection;
ptrNode = m_ptrCurrentCollection->Insert<CTable>(sdv::toml::npos, rrangeKeyPath, true);
ptrNode = m_ptrCurrentCollection->AddNodeFromRange<CTable>(rrangeKeyPath, true);
m_ptrCurrentCollection = ptrNode->Cast<CNodeCollection>();
m_stackEnvironment.push(EEnvironment::inline_table);
ProcessInlineTable(rNodeRange);

View File

@@ -18,6 +18,7 @@
#include "lexer_toml.h"
#include "parser_node_toml.h"
#include "miscellaneous.h"
#include "parser_node_indexer.h"
#include <stack>
#include <memory>
#include <string>
@@ -25,7 +26,9 @@
/// The TOML parser namespace
namespace toml_parser
{
// Forward declarations
class CNode;
class CNodeCollection;
/**
* @brief Creates a tree structure from input of UTF-8 encoded TOML source data
@@ -33,6 +36,10 @@ namespace toml_parser
class CParser : public sdv::IInterfaceAccess, public sdv::toml::ITOMLParser
{
public:
// Forward declaration
class CLockRebuild;
friend CLockRebuild; ///< Friend class can trigger manage rebuild lock counter.
/**
* @brief Construct a new Parser object
* @param[in] rssString UTF-8 encoded data of a TOML source
@@ -66,6 +73,12 @@ namespace toml_parser
*/
CLexer& Lexer();
/**
* @brief Get the indexer object managing the overall order of the nodes.
* @return Reference to the index object.
*/
CNodeIndexer& Indexer();
/**
* @{
* @brief Return the root node.
@@ -85,7 +98,65 @@ namespace toml_parser
*/
std::string GenerateTOML(const std::string& rssPrefixKey = std::string()) const;
/**
* @brief Lock rebuild object preventing rebuilding the node order of all the tables.
*/
class CLockRebuild
{
friend CParser; ///< Parser can access the constructor
/**
* @brief Constructor
* @param[in] rParser Reference to the parser object
*/
CLockRebuild(CParser& rParser);
public:
/**
* @brief Copy constructor
* @param[in] rLockRebuild Reference to another rebuild lock object.
*/
CLockRebuild(const CLockRebuild& rLockRebuild);
/**
* @brief Move constructor
* @param[in] rLockRebuild Reference to another rebuild lock object.
*/
CLockRebuild(CLockRebuild&& rLockRebuild);
/**
* @brief Destructor
*/
~CLockRebuild();
private:
CParser& m_rParser; ///< Reference to the parser object
};
/**
* @brief Create a rebuild lock object. During the lifetime of the object rebuilding the node order is locked using the lock
* counter method.
* @return An instance of the rebuild lock object.
*/
CLockRebuild CreateRebuildLockObject();
/**
* @brief Returns whether rebuild is locked at the moment.
* @return Set when rebuild is locked.
*/
bool RebuildLocked() const;
private:
/**
* @brief Increase the rebuild lock counter. A lock count larger than 0 will prevent a rebuild.
*/
void IncrRebuildLockCnt();
/**
* @brief Decrease the rebuild lock counter. A lock count of 0 will trigger the rebuild.
*/
void DecrRebuildLockCnt();
/**
* @brief Process a table declaration.
* @param[in, out] rNodeRange Reference to the extended token range of the node.
@@ -139,10 +210,13 @@ namespace toml_parser
inline_table ///< Environment for a table
};
enum_stack<EEnvironment, EEnvironment::none> m_stackEnvironment; ///< Tracking of environments in nested structures.
std::shared_ptr<CRootTable> m_ptrRoot; ///< The one root node.
std::shared_ptr<CNodeCollection> m_ptrCurrentCollection; ///< The current collection node.
CLexer m_lexer; ///< Lexer.
enum_stack<EEnvironment, EEnvironment::none> m_stackEnvironment; ///< Tracking of environments in nested structures.
std::shared_ptr<CRootTable> m_ptrRoot; ///< The one root node.
std::shared_ptr<CNodeCollection> m_ptrCurrentCollection; ///< The current collection node.
CLexer m_lexer; ///< Lexer object user for lexing the TOML code.
CNodeIndexer m_indexer; ///< Indexer managing the oberall order of the nodes.
size_t m_nRebuildLockCnt = 0; ///< A lock counter > 0 will prevent the node order
///< rebuild.
};
} // namespace toml_parser

View File

@@ -25,8 +25,6 @@ add_library(ipc_com SHARED
"com_channel.cpp"
"marshall_object.h"
"marshall_object.cpp"
#"scheduler.cpp"
)
if(UNIX)
target_link_libraries(ipc_com rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})

View File

@@ -18,7 +18,7 @@
#include <support/serdes.h>
#include <support/local_service_access.h>
#include <interfaces/serdes/core_ps_serdes.h>
#include "../../global/scheduler/scheduler.cpp"
#include "../../global/scheduler/scheduler.h"
CChannelConnector::CChannelConnector(CCommunicationControl& rcontrol, uint32_t uiIndex, sdv::IInterfaceAccess* pChannelEndpoint) :
m_rcontrol(rcontrol), m_ptrChannelEndpoint(pChannelEndpoint),

View File

@@ -95,8 +95,8 @@ public:
* @brief Sends data consisting of multiple data chunks via the IPC connection.
* @param[in] tProxyID Marshall ID of the proxy (source).
* @param[in] tStubID Marshall ID of the stub (target).
* @param[in] rseqInputData Sequence of data buffers to be sent. May be altered during processing to add/change the sequence content
* without having to copy the data.
* @param[in] rseqInputData Sequence of data buffers to be sent. May be altered during processing to add/change the sequence
* content without having to copy the data.
* @return Returns the results of the call or throws a marshall exception.
*/
sdv::sequence<sdv::pointer<uint8_t>> MakeCall(sdv::ps::TMarshallID tProxyID, sdv::ps::TMarshallID tStubID,
@@ -155,7 +155,9 @@ private:
sdv::ipc::IDataSend* m_pDataSend = nullptr; ///< Pointer to the send interface.
std::mutex m_mtxCalls; ///< Call map protection.
std::map<uint64_t, SCallEntry&> m_mapCalls; ///< call map.
CTaskScheduler m_scheduler; ///< Scheduler to process incoming calls.
CTaskScheduler<sdv::core::secure_thread> m_scheduler; ///< Scheduler to process incoming calls.
sdv::core::IPermissionControl* m_pPermissionControl = nullptr; ///< Pointer to the permission control interface of the
///< target system.
};
#endif // !defined COM_CHANNEL_H

View File

@@ -16,6 +16,8 @@
#include <support/toml.h>
#include "com_channel.h"
#include "marshall_object.h"
#include <support/app_control.h>
#include <support/any.h>
thread_local CChannelConnector* CCommunicationControl::m_pConnectorContext = nullptr;
@@ -40,7 +42,7 @@ void CCommunicationControl::OnShutdown()
std::unique_lock<std::mutex> lock(m_mtxChannels);
auto vecInitialConnectMon = std::move(m_vecInitialConnectMon);
lock.unlock();
for (std::thread& rthread : vecInitialConnectMon)
for (sdv::core::secure_thread& rthread : vecInitialConnectMon)
{
if (rthread.joinable())
rthread.join();
@@ -63,10 +65,11 @@ sdv::com::TConnectionID CCommunicationControl::CreateServerConnection(/*in*/ sdv
switch (eChannelType)
{
case sdv::com::EChannelType::local_channel:
ssChannelServer = "LocalChannelControl";
ssChannelServer = static_cast<std::string>(sdv::app::GetAppSettingsAttribute("Communication.DefaultProvider"));
break;
case sdv::com::EChannelType::remote_channel:
ssChannelServer = "RemoteChannelControl";
// Currently not supported
ssChannelServer = "UnknownComProvider";
break;
default:
return {};

View File

@@ -196,7 +196,7 @@ public:
private:
std::mutex m_mtxChannels; ///< Protect the channel map.
std::vector<std::shared_ptr<CChannelConnector>> m_vecChannels; ///< Channel vector.
std::vector<std::thread> m_vecInitialConnectMon; ///< Initial connection monitor.
std::vector<sdv::core::secure_thread> m_vecInitialConnectMon; ///< Initial connection monitor.
std::recursive_mutex m_mtxObjects; ///< Protect object vectors.
std::vector<std::weak_ptr<CMarshallObject>> m_vecMarshallObjects; ///< Vector with marshall objects; lifetime is handled by channel.
std::map<sdv::interface_t, std::shared_ptr<CMarshallObject>> m_mapStubObjects; ///< Map of interfaces to stub objects

View File

@@ -1,3 +1,4 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
@@ -18,7 +19,7 @@
#include <support/pssup.h>
#include <interfaces/app.h>
CRepositoryProxy::CRepositoryProxy(CClient& rClient, sdv::com::TConnectionID tConnection,
CRepositoryProxy::CRepositoryProxy(CClientConnect& rClient, sdv::com::TConnectionID tConnection,
sdv::IInterfaceAccess* pRepositoryProxy) :
m_rClient(rClient), m_tConnection(tConnection), m_ptrRepositoryProxy(pRepositoryProxy)
{}
@@ -26,120 +27,131 @@ CRepositoryProxy::CRepositoryProxy(CClient& rClient, sdv::com::TConnectionID tCo
void CRepositoryProxy::DestroyObject()
{
// Call the client to disconnect the connection and destroy the object.
m_rClient.Disconnect(m_tConnection);
m_rClient.Disconnect();
}
bool CClient::OnInitialize()
sdv::com::TConnectionID CRepositoryProxy::GetConnectionID() const
{
return true;
return m_tConnection;
}
void CClient::OnShutdown()
{
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
if (!pConnectionControl)
SDV_LOG_ERROR("Failed to get communication control!");
// Disconnect from all repositories
std::unique_lock<std::mutex> lock(m_mtxRepositoryProxies);
auto mapRepositoryProxiesCopy = std::move(m_mapRepositoryProxies);
lock.unlock();
if (pConnectionControl)
{
for (const auto& rvtRepository : mapRepositoryProxiesCopy)
pConnectionControl->RemoveConnection(rvtRepository.first);
}
}
sdv::IInterfaceAccess* CClient::Connect(const sdv::u8string& ssConnectString)
bool CClientConnect::OnInitialize()
{
const sdv::app::IAppContext* pContext = sdv::core::GetCore<sdv::app::IAppContext>();
if (!pContext)
{
SDV_LOG_ERROR("Failed to get application context!");
return nullptr;
return false;
}
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
if (!pConnectionControl)
{
SDV_LOG_ERROR("Failed to get communication control!");
return nullptr;
return false;
}
sdv::ipc::IChannelAccess* pChannelAccess = nullptr;
std::string ssConfig;
try
// Check for a provider. Without provider there is no object to use for listening.
if (m_ssProvider.empty())
{
// Determine whether the service is running as server or as client.
sdv::toml::CTOMLParser config(ssConnectString);
std::string ssType = config.GetDirect("Client.Type").GetValue();
if (ssType.empty()) ssType = "Local";
if (ssType == "Local")
{
uint32_t uiInstanceID = config.GetDirect("Client.Instance").GetValue();
pChannelAccess = sdv::core::GetObject<sdv::ipc::IChannelAccess>("LocalChannelControl");
if (!pChannelAccess)
{
SDV_LOG_ERROR("No local channel control or channel control not configured as client!");
return nullptr;
}
ssConfig = std::string(R"code([IpcChannel]
Name = "LISTENER_)code") + std::to_string(uiInstanceID ? uiInstanceID : pContext->GetInstanceID()) + R"code("
)code";
}
else if (ssType == "Remote")
{
std::string ssInterface = config.GetDirect("Client.Interface").GetValue();
uint32_t uiPort = config.GetDirect("Client.Interface").GetValue();
if (ssInterface.empty() || !uiPort)
{
SDV_LOG_ERROR("Missing interface or port number to initialize a remote client!");
return nullptr;
}
pChannelAccess = sdv::core::GetObject<sdv::ipc::IChannelAccess>("RemoteChannelControl");
if (!pChannelAccess)
{
SDV_LOG_ERROR("No remote channel control or channel control not configured as client!");
return nullptr;
}
ssConfig = R"code([IpcChannel]
Interface = ")code" + ssInterface + R"code(
Port = ")code" + std::to_string(uiPort) + R"code(
)code";
}
else
{
SDV_LOG_ERROR("Invalid or missing listener configuration for listener service!");
SetObjectIntoConfigErrorState();
return nullptr;
}
SDV_LOG_ERROR("Missing provider name for creating a client object!");
return false;
}
catch (const sdv::toml::XTOMLParseException& rexcept)
sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject<sdv::ipc::IChannelAccess>(m_ssProvider);
if (!pChannelAccess)
{
SDV_LOG_ERROR("Invalid service configuration for listener service: ", rexcept.what(), "!");
SetObjectIntoConfigErrorState();
return nullptr;
SDV_LOG_ERROR("Cannot instantiate provider '", m_ssProvider, "' for the creation of a client object!");
return false;
}
// The connection will be established in the Connect function.
return true;
}
void CClientConnect::OnShutdown()
{
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
if (!pConnectionControl)
SDV_LOG_ERROR("Failed to get communication control!");
// Disconnect
Disconnect();
}
bool CClientConnect::Connect()
{
sdv::u8string ssProvider;
sdv::u8string ssConfig;
{
std::unique_lock<std::mutex> lock(m_mtx);
ssProvider = m_ssProvider;
ssConfig = BuildObjectConfig();
}
const sdv::app::IAppContext* pContext = sdv::core::GetCore<sdv::app::IAppContext>();
if (!pContext)
{
SDV_LOG_ERROR("Failed to get application context!");
return false;
}
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
if (!pConnectionControl)
{
SDV_LOG_ERROR("Failed to get communication control!");
return false;
}
// Check for a provider. Without provider there is no object to use for listening.
if (ssProvider.empty())
{
SDV_LOG_ERROR("Missing provider name for creating a client object!");
return false;
}
sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject<sdv::ipc::IChannelAccess>(ssProvider);
if (!pChannelAccess)
{
SDV_LOG_ERROR("Cannot instantiate provider '", m_ssProvider, "' for the creation of a client object!");
return false;
}
// Get the repository
sdv::TInterfaceAccessPtr ptrRespository = sdv::core::GetObject("RepositoryService");
if (!ptrRespository)
{
SDV_LOG_ERROR("Failed to get repository service!");
return false;
}
// Add a dependency of the provider to this service (to prevent the provider to be terminated while the service is still
// running).
sdv::core::IObjectDependency* pObjectDependency = ptrRespository.GetInterface<sdv::core::IObjectDependency>();
if (!pObjectDependency)
{
SDV_LOG_ERROR("Failed to get the object dependency interface!");
return false;
}
pObjectDependency->AddObjectDependency(Self().ssName, ssProvider);
// First access the listener channel. This allows us to access the channel creation interface.
// TODO: Use named mutex to prevent multiple connections at the same time.
// Connect to the channel.
sdv::TObjectPtr ptrListenerEndpoint = pChannelAccess->Access(ssConfig);
// Assign the endpoint to the communication service.
sdv::IInterfaceAccess* pListenerProxy = nullptr;
sdv::com::TConnectionID tListenerConnection = pConnectionControl->AssignClientEndpoint(ptrListenerEndpoint, 5000,
pListenerProxy);
sdv::com::TConnectionID tListenerConnection =
pConnectionControl->AssignClientEndpoint(ptrListenerEndpoint, 5000, pListenerProxy);
ptrListenerEndpoint.Clear(); // Lifetime has been taken over by communication control.
if (!tListenerConnection || !pListenerProxy)
{
SDV_LOG_ERROR("Could not assign the client endpoint!");
if (tListenerConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tListenerConnection);
return nullptr;
return false;
}
sdv::TInterfaceAccessPtr ptrListenerProxy(pListenerProxy);
@@ -149,9 +161,26 @@ Port = ")code" + std::to_string(uiPort) + R"code(
{
SDV_LOG_ERROR("Could not get the channel creation interface!");
if (tListenerConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tListenerConnection);
return nullptr;
return false;
}
sdv::u8string ssConnectionString = pRequestChannel->RequestChannel("");
sdv::u8string ssRequestConfig;
// Tunnel providers need the full object config so the private channel
// can preserve provider-specific fields such as the tunnel name.
// For shared memory we keep the legacy behavior and let the provider
// create its own private channel details.
if (ssProvider == "unix_domain_sockets_tunnel")
{
ssRequestConfig = ssConfig;
}
else
{
ssRequestConfig.clear();
}
sdv::u8string ssConnectionString = pRequestChannel->RequestChannel(ssRequestConfig);
// Disconnect from the listener
if (tListenerConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tListenerConnection);
@@ -159,7 +188,7 @@ Port = ")code" + std::to_string(uiPort) + R"code(
if (ssConnectionString.empty())
{
SDV_LOG_ERROR("Could not get the private channel connection information!");
return nullptr;
return false;
}
// TODO: Use named mutex to prevent multiple connections at the same time.
@@ -174,30 +203,64 @@ Port = ")code" + std::to_string(uiPort) + R"code(
{
SDV_LOG_ERROR("Could not assign the client endpoint to the private channel!");
if (tPrivateConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tPrivateConnection);
return nullptr;
return false;
}
// Create a remote repository object
std::unique_lock<std::mutex> lock(m_mtxRepositoryProxies);
m_mapRepositoryProxies.try_emplace(tPrivateConnection, *this, tPrivateConnection, pPrivateProxy);
m_ptrRemoteRepo = std::make_shared<CRepositoryProxy>(*this, tPrivateConnection, pPrivateProxy);
return pPrivateProxy;
return true;
}
void CClient::Disconnect(sdv::com::TConnectionID tConnectionID)
bool CClientConnect::Disconnect()
{
// Find the connection, disconnect and remove the connection from the repository list.
std::unique_lock<std::mutex> lock(m_mtxRepositoryProxies);
auto itRepository = m_mapRepositoryProxies.find(tConnectionID);
if (itRepository == m_mapRepositoryProxies.end()) return;
std::unique_lock<std::mutex> lock(m_mtx);
if (!m_ptrRemoteRepo) return false;
// Whatever happens, the connection will be removed
std::shared_ptr<CRepositoryProxy> ptrRemoteRepoLocal = std::move(m_ptrRemoteRepo);
// Disconnect
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
if (!pConnectionControl)
{
SDV_LOG_ERROR("Failed to get communication control!");
else
pConnectionControl->RemoveConnection(itRepository->first);
return false;
}
pConnectionControl->RemoveConnection(ptrRemoteRepoLocal->GetConnectionID());
// Remove entry
m_mapRepositoryProxies.erase(itRepository);
// Get the repository
sdv::TInterfaceAccessPtr ptrRespository = sdv::core::GetObject("RepositoryService");
if (!ptrRespository)
{
SDV_LOG_ERROR("Failed to get repository service!");
return false;
}
// Add a dependency of the provider to this service (to prevent the provider to be terminated while the service is still
// running).
sdv::core::IObjectDependency* pObjectDependency = ptrRespository.GetInterface<sdv::core::IObjectDependency>();
if (!pObjectDependency)
{
SDV_LOG_ERROR("Failed to get the object dependency interface!");
return false;
}
pObjectDependency->RemoveObjectDependency("ClientConnectService", m_ssProvider);
return true;
}
bool CClientConnect::IsConnected() const
{
std::unique_lock<std::mutex> lock(m_mtx);
return m_ptrRemoteRepo ? true : false;
}
sdv::IInterfaceAccess* CClientConnect::GetRemoteRepository()
{
std::unique_lock<std::mutex> lock(m_mtx);
return m_ptrRemoteRepo ? m_ptrRemoteRepo.get() : nullptr;
}

View File

@@ -19,7 +19,7 @@
#include <interfaces/com.h>
// Forward declaration.
class CClient;
class CClientConnect;
/**
* @brief Class managing the connection and providing access to the server repository through a proxy.
@@ -33,7 +33,7 @@ public:
* @param[in] tConnection The connection ID to the server.
* @param[in] pRepositoryProxy Proxy to the server repository.
*/
CRepositoryProxy(CClient& rClient, sdv::com::TConnectionID tConnection, sdv::IInterfaceAccess* pRepositoryProxy);
CRepositoryProxy(CClientConnect& rClient, sdv::com::TConnectionID tConnection, sdv::IInterfaceAccess* pRepositoryProxy);
/**
* @brief Do not allow a copy constructor.
@@ -59,8 +59,14 @@ public:
*/
virtual void DestroyObject() override;
/**
* @brief Get the connection ID for this connection.
* @return The connection ID.
*/
sdv::com::TConnectionID GetConnectionID() const;
private:
CClient& m_rClient; ///< Reference to the client object.
CClientConnect& m_rClient; ///< Reference to the client object.
sdv::com::TConnectionID m_tConnection = {}; ///< Connection ID.
sdv::TInterfaceAccessPtr m_ptrRepositoryProxy; ///< Smart pointer to the remote repository.
};
@@ -68,7 +74,7 @@ private:
/**
* @brief Client object
*/
class CClient : public sdv::CSdvObject, public sdv::com::IClientConnect
class CClientConnect : public sdv::CSdvObject, public sdv::com::IClientConnect
{
public:
// Interface map
@@ -78,8 +84,15 @@ public:
// Object declaration
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("ConnectionService")
DECLARE_OBJECT_SINGLETON()
DECLARE_OBJECT_CLASS_NAME("ClientConnectService")
DECLARE_OBJECT_DEPENDENCIES("CommunicationControl")
// Parameter map
BEGIN_SDV_PARAM_MAP()
SDV_PARAM_ENABLE_LOCKING()
SDV_PARAM_GROUP("Provider")
SDV_PARAM_ENTRY(m_ssProvider, "Name", "", "", "Provider name to create a connection for.")
END_SDV_PARAM_MAP()
/**
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
@@ -93,40 +106,40 @@ public:
virtual void OnShutdown() override;
/**
* @brief Connect to a remote system using the connection string to contact the system. Overload of
* sdv::com::IClientConnect::Connect.
* @remarks After a successful connection, the ConnectClient utility is not needed any more.
* @param[in] ssConnectString Optional connection string to use for connection. If not provided, the connection will
* automatically get the connection ID from the app-control service (default). The connection string for a local
* connection can be of the form:
* @code
* [Client]
* Type = "Local"
* Instance = 1234 # Optional: only use when connecting to a system with a different instance ID.
* @endcode
* And the following can be used for a remote connection:
* @code
* [Client]
* Type = "Remote"
* Interface = "127.0.0.1"
* Port = 2000
* @endcode
* @return Returns an interface to the repository of the remote system or a NULL pointer if not found.
* @brief Connect to a remote system. Overload of IClientConnect::Connect.
* @return Returns whether connect was successful.
*/
virtual sdv::IInterfaceAccess* Connect(const sdv::u8string& ssConnectString) override;
virtual bool Connect() override;
/**
* @brief Disconnect and remove the remote repository object.
* @param[in] tConnectionID The ID of the connection.
* @brief Disconnect from a connected system. Overload of IClientConnect::Disconnect.
* @return Returns whether disconnect was successful.
*/
void Disconnect(sdv::com::TConnectionID tConnectionID);
bool Disconnect() override;
/**
* @brief State of the current connection. Overload of IClientConnect::IsConnected.
* @return Returns whether an active connection exists.
* @return The connect state.
*/
bool IsConnected() const override;
/**
* @brief Get the remote repository that is available after connection. Overload of IClientConnect::GetRemoteRepository.
* @remarks For main, isolated and external applications, the remote repository will be automatically linked to the
* local repository. Hence a requests for the repository is not needed. For all other applications, access must be
* explicitly acquired through this interface.
* @return Interface to the remote repository if a successful connection is established. The interface is valid until
* disconnect is called or the client connection service is terminated.
*/
sdv::IInterfaceAccess* GetRemoteRepository() override;
private:
std::mutex m_mtxRepositoryProxies; ///< Protect access to the remnote repository map.
std::map<sdv::com::TConnectionID, CRepositoryProxy> m_mapRepositoryProxies; ///< map of remote repositories.
mutable std::mutex m_mtx; ///< Protect against multiple parallel connection activities.
sdv::u8string m_ssProvider; ///< Name of the provider to use for the listening.
std::shared_ptr<CRepositoryProxy> m_ptrRemoteRepo; ///< Interface to the remote repository.
};
DEFINE_SDV_OBJECT(CClient)
DEFINE_SDV_OBJECT(CClientConnect)
#endif // !defined CLIENT_H

View File

@@ -16,11 +16,12 @@
#include <interfaces/com.h>
#include <interfaces/app.h>
#include <support/pssup.h>
#include <support/local_service_access.h>
CChannelBroker::CChannelBroker(CListener& rListener) : m_rListener(rListener)
{}
sdv::u8string CChannelBroker::RequestChannel(/*in*/ const sdv::u8string& /*ssConfig*/)
sdv::u8string CChannelBroker::RequestChannel(/*in*/ const sdv::u8string& ssConfig)
{
// Get the communication control
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
@@ -39,26 +40,30 @@ sdv::u8string CChannelBroker::RequestChannel(/*in*/ const sdv::u8string& /*ssCon
}
// Get the channel control.
sdv::ipc::ICreateEndpoint* pEndpoint = nullptr;
if (m_rListener.IsLocalListener())
pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>("LocalChannelControl");
else
pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>("RemoteChannelControl");
sdv::ipc::ICreateEndpoint* pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>(m_rListener.GetProviderName());
if (!pEndpoint)
{
SDV_LOG_ERROR("No local channel control!");
return {};
}
// Create the endpoint
sdv::ipc::SChannelEndpoint sEndpoint = pEndpoint->CreateEndpoint(sdv::u8string());
// Forward protocol-specific configuration to the provider when creating the private channel.
SDV_LOG_INFO("[IPC_CONNECT][Listener] RequestChannel input config:\n", ssConfig);
sdv::ipc::SChannelEndpoint sEndpoint = pEndpoint->CreateEndpoint(ssConfig);
if (!sEndpoint.pConnection)
{
SDV_LOG_ERROR("Could not create the endpoint for channel request!");
return sdv::u8string();
return {};
}
SDV_LOG_INFO("[IPC_CONNECT][Listener] RequestChannel produced connect string: ", sEndpoint.ssConnectString);
sdv::TObjectPtr ptrEndpoint(sEndpoint.pConnection); // Does automatic destruction if failure happens.
// Restrict access permissions
sdv::core::CAccessPermission permission = sdv::core::RestrictAccessPermission(sdv::core::EAccessPermission::local_access);
// Assign the endpoint to the communication service.
sdv::com::TConnectionID tConnection = pConnectionControl->AssignServerEndpoint(ptrEndpoint, ptrRespository, 100, false);
ptrEndpoint.Clear(); // Lifetime taken over by communication control.
@@ -93,49 +98,21 @@ bool CListener::OnInitialize()
return false;
}
sdv::ipc::ICreateEndpoint* pEndpoint = nullptr;
std::string ssConfig;
if (m_ssType == "Local")
// Check for a provider. Without provider there is no object to use for listening.
if (m_ssProvider.empty())
{
m_bLocalListener = true;
pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>("LocalChannelControl");
if (!pEndpoint)
{
SDV_LOG_ERROR("No local channel control!");
return false;
}
// Request the instance ID from the app control
ssConfig = std::string(R"code([IpcChannel]
Name = "LISTENER_)code") + std::to_string(m_uiInstanceID ? m_uiInstanceID : pContext->GetInstanceID()) + R"code("
Size = 2048
)code";
}
else if (m_ssType == "Remote")
{
m_bLocalListener = false;
if (m_ssInterface.empty() || !m_uiPort)
{
SDV_LOG_ERROR("Missing interface or port number to initialize a remote listener!");
return false;
}
pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>("RemoteChannelControl");
if (!pEndpoint)
{
SDV_LOG_ERROR("No remote channel control!");
return false;
}
ssConfig = R"code([IpcChannel]
Interface = ")code" + m_ssInterface + R"code(
Port = ")code" + std::to_string(m_uiPort) + R"code(
)code";
}
else
{
SDV_LOG_ERROR("Invalid or missing listener configuration for listener service!");
SDV_LOG_ERROR("Missing provider name for creating a listener object!");
return false;
}
sdv::ipc::ICreateEndpoint* pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>(m_ssProvider);
if (!pEndpoint)
{
SDV_LOG_ERROR("Cannot instantiate provider '", m_ssProvider, "' for the creation of a listener object!");
return false;
}
// Get the IpcChannel information from the the configuration
auto ssConfig = BuildObjectConfig();
// Create the endpoint
sdv::ipc::SChannelEndpoint sEndpoint = pEndpoint->CreateEndpoint(ssConfig);
@@ -146,6 +123,24 @@ Port = ")code" + std::to_string(m_uiPort) + R"code(
}
sdv::TObjectPtr ptrEndpoint(sEndpoint.pConnection); // Does automatic destruction if failure happens.
// Get the repository
sdv::TInterfaceAccessPtr ptrRespository = sdv::core::GetObject("RepositoryService");
if (!ptrRespository)
{
SDV_LOG_ERROR("Failed to get repository service!");
return false;
}
// Add a dependency of the provider to this service (to prevent the provider to be terminated while the service is still
// running).
sdv::core::IObjectDependency* pObjectDependency = ptrRespository.GetInterface<sdv::core::IObjectDependency>();
if (!pObjectDependency)
{
SDV_LOG_ERROR("Failed to get the object dependency interface!");
return false;
}
pObjectDependency->AddObjectDependency(Self().ssName, m_ssProvider);
// Assign the endpoint to the communication service.
m_tConnection = pConnectionControl->AssignServerEndpoint(ptrEndpoint, &m_broker, 100, true);
ptrEndpoint.Clear(); // Lifetime taken over by communication control.
@@ -173,9 +168,8 @@ void CListener::OnShutdown()
m_ptrConnection.Clear();
}
bool CListener::IsLocalListener() const
const sdv::u8string& CListener::GetProviderName()
{
return m_bLocalListener;
return m_ssProvider;
}

View File

@@ -52,6 +52,25 @@ private:
/**
* @brief Listener object
* @details the lister is instantiated using the following parameter information
* @code
* # Provider to use for listening
* [Provider]
* Name = ""
*
* # Additional channel information for the listener (needed for unique listener identification)
* [IpcChannel]
* xyz = ""
* @endcode
*
* For example for shared memory:
* @code
* [Provider]
* Name = "DefaultSharedMemory"
* [IpcChannel]
* Name = "LISTENER_1234"
* Size = 10240
* @endcode
*/
class CListener : public sdv::CSdvObject
{
@@ -63,34 +82,18 @@ public:
// Object declaration
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("ConnectionListenerService")
DECLARE_OBJECT_CLASS_NAME("ListenerConnectService")
DECLARE_OBJECT_DEPENDENCIES("CommunicationControl")
// Parameter map
BEGIN_SDV_PARAM_MAP()
SDV_PARAM_ENABLE_LOCKING()
SDV_PARAM_GROUP("Listener")
SDV_PARAM_ENTRY(m_ssType, "Type", "Local", "", "The type of listener \"Local\" or \"Remote\".")
SDV_PARAM_ENTRY(m_uiInstanceID, "Instance", 0, "", "The instance ID to listen for.")
SDV_PARAM_ENTRY(m_ssInterface, "Interface", "", "", "Interface identification.")
SDV_PARAM_ENTRY(m_uiPort, "Port", 0, "", "Port number for connection.")
SDV_PARAM_GROUP("Provider")
SDV_PARAM_ENTRY(m_ssProvider, "Name", "", "", "Provider name to create a listener for.")
END_SDV_PARAM_MAP()
/**
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
* @details The object configuration contains the information needed to start the listener. The following configuration is
* available for the local listener:
* @code
* [Listener]
* Type = "Local"
* Instance = 1000 # Normally not used; system instance ID is used automatically.
* @endcode
* And the following is available for a remote listener:
* @code
* [Listener]
* Type = "Remote"
* Interface = "127.0.0.1"
* Port = 2000
* @endcode
* @return Returns 'true' when the initialization was successful, 'false' when not.
*/
virtual bool OnInitialize() override;
@@ -101,19 +104,15 @@ public:
virtual void OnShutdown() override;
/**
* @brief When set, the listener is configured to be a local listener. Otherwise the listerner is configured as remote listener.
* @return Boolean set when local lostener.
* @brief Get the provider name used by this listener.
* @return Reference to the provider name.
*/
bool IsLocalListener() const;
const sdv::u8string& GetProviderName();
private:
sdv::u8string m_ssType; ///< Listener type: "Local" or "Remote"
uint32_t m_uiInstanceID = 0; ///< Instance ID to listen for.
std::string m_ssInterface; ///< Interface string for remote listener.
uint32_t m_uiPort = 0; ///< Port for remote listener.
sdv::u8string m_ssProvider; ///< Name of the provider to use for the listening.
sdv::TObjectPtr m_ptrConnection; ///< The connection object.
CChannelBroker m_broker; ///< Channel broker, used to request new channels
bool m_bLocalListener = true; ///< When set, the listener is a local listener; otherwise a remote listener.
sdv::com::TConnectionID m_tConnection = {}; ///< Channel connection ID.
};

View File

@@ -56,15 +56,15 @@ sdv::IInterfaceAccess* CSharedMemChannelMgnt::Access(const sdv::u8string& ssConn
sdv::toml::CTOMLParser parser(ssConnectString);
if (!parser.IsValid()) return nullptr;
// Is this a configuration provided by the endpoint (uses a "Provider" key), then this is a connection string. Use this
// Is this a configuration provided by the endpoint (uses a "ConnectParam" key), then this is a connection string. Use this
// to connect to the shared memory.
std::shared_ptr<CConnection> ptrConnection;
if (parser.GetDirect("Provider").IsValid())
if (parser.GetDirect("ConnectParam").IsValid())
ptrConnection = std::make_shared<CConnection>(m_watchdog, ssConnectString.c_str());
else
{
std::string ssName = static_cast<std::string>(parser.GetDirect("IpcChannel.Name").GetValue());
ptrConnection = std::make_shared<CConnection>(m_watchdog, 0,ssName, false);
ptrConnection = std::make_shared<CConnection>(m_watchdog, 0, ssName, false);
}
if (!ptrConnection) return {};
m_watchdog.AddConnection(ptrConnection);

View File

@@ -43,9 +43,7 @@ public:
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("DefaultSharedMemoryChannelControl")
DECLARE_OBJECT_CLASS_ALIAS("LocalChannelControl")
DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl")
DECLARE_OBJECT_CLASS_NAME("DefaultSharedMemory")
DECLARE_OBJECT_SINGLETON()
/**

View File

@@ -117,7 +117,7 @@ std::string CConnection::GetConnectionString()
{
// The connection string is of the form:
// [Provider]
// Name = "DefaultSharedMemoryChannelControl"
// Name = "DefaultSharedMemory"
//
// [[ConnectParam]]
// Type = "shared_mem"
@@ -132,9 +132,9 @@ std::string CConnection::GetConnectionString()
// SyncTx = "SDV_TX_SYNC_REQUEST_1234"
// SyncRx = "SDV_RX_SYNC_REQUEST_1234"
// Direction = "request"
std::string ssConnectionString = R"code([Provider]
Name = "LocalChannelControl"
)code" + std::string("\n") + m_sender.GetConnectionString() + "\n" + m_receiver.GetConnectionString();
std::string ssConnectionString = R"toml([Provider]
Name = "DefaultSharedMemory"
)toml" + std::string("\n") + m_sender.GetConnectionString() + "\n" + m_receiver.GetConnectionString();
return ssConnectionString;
}
@@ -352,7 +352,7 @@ bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
std::unique_lock<std::mutex> lock(m_mtxConnect);
// Allowed to connect?
if (m_eConnectState != sdv::ipc::EConnectState::uninitialized)
if (m_eConnectState != sdv::ipc::EConnectState::uninitialized && m_eConnectState != sdv::ipc::EConnectState::disconnected)
{
for (auto& rprEventCallback : m_lstEventCallbacks)
if (rprEventCallback.pCallback && rprEventCallback.uiCookie)
@@ -377,9 +377,9 @@ bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
SetConnectState(sdv::ipc::EConnectState::initialized);
// Start the receiving thread (wait until started).
m_threadReceive = std::thread(&CConnection::ReceiveMessages, this);
m_threadReceive = sdv::core::secure_thread(&CConnection::ReceiveMessages, this);
#if ENABLE_DECOUPLING > 0
m_threadDecoupleReceive = std::thread(&CConnection::DecoupleReceive, this);
m_threadDecoupleReceive = sdv::core::secure_thread(&CConnection::DecoupleReceive, this);
#endif
if (!m_bStarted)
m_cvStartConnect.wait_for(lock, std::chrono::milliseconds(1000));
@@ -602,9 +602,10 @@ void CConnection::ReceiveMessages()
auto optPacket = m_receiver.TryRead();
if (!optPacket)
{
// TODO EVE: Also allow synchronization take place when disconnected.
// 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_eConnectState == sdv::ipc::EConnectState::disconnected ||*/ m_eConnectState == sdv::ipc::EConnectState::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();
@@ -1001,9 +1002,10 @@ void CConnection::ReceiveConnectTerm(CMessage& /*rMessage*/)
m_sender.CancelSend();
m_sender.ResetRx();
// Send sync request (do not wait until next round in case a very short connection <100ms took place).
if (m_bServer)
Send(SMsgHdr{ SDVFrameworkInterfaceVersion, EMsgType::sync_request });
// TODO EVE: Disabled, since state of client straight goes into "connecting" after being disconnected.
//// Send sync request (do not wait until next round in case a very short connection <100ms took place).
//if (m_bServer)
// Send(SMsgHdr{ SDVFrameworkInterfaceVersion, EMsgType::sync_request });
}
void CConnection::ReceiveDataMessage(CMessage& rMessage, SDataContext& rsDataCtxt)

View File

@@ -257,7 +257,7 @@ private:
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage module lifetime.
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.
sdv::core::secure_thread m_threadReceive; ///< Thread which receives data from the socket.
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
@@ -275,7 +275,7 @@ private:
#if ENABLE_DECOUPLING > 0
std::mutex m_mtxReceive; ///< Protect receive queue.
std::queue<sdv::sequence<sdv::pointer<uint8_t>>> m_queueReceive; ///< Receive queue to decouple receiving and processing.
std::thread m_threadDecoupleReceive; ///< Decoupled receive thread.
sdv::core::secure_thread m_threadDecoupleReceive; ///< Decoupled receive thread.
std::condition_variable m_cvReceiveAvailable; ///< Condition variable synchronizing the processing.
std::condition_variable m_cvReceiveProcessed; ///< Condition variable synchronizing the processing.
#endif

View File

@@ -22,7 +22,7 @@
CWatchDog::CWatchDog()
{
m_threadScheduledConnectionDestructions = std::thread(&CWatchDog::HandleScheduledConnectionDestructions, this);
m_threadScheduledConnectionDestructions = sdv::core::secure_thread(&CWatchDog::HandleScheduledConnectionDestructions, this);
}
CWatchDog::~CWatchDog()

View File

@@ -34,6 +34,7 @@
#include <interfaces/process.h>
#include <support/interface_ptr.h>
#include <support/local_service_access.h>
#include <mutex>
#include <map>
#include <memory>
@@ -127,7 +128,7 @@ private:
std::condition_variable m_cvTriggerConnectionDestruction; ///< Condition variable used to trigger when a
///< connection is scheduled for destruction.
std::queue<std::shared_ptr<CConnection>> m_queueScheduledConnectionDestructions; ///< Scheduled connection for destruction.
std::thread m_threadScheduledConnectionDestructions; ///< Thread processing the scheduled destructions.
sdv::core::secure_thread m_threadScheduledConnectionDestructions; ///< Thread processing the scheduled destructions.
std::atomic_bool m_bShutdown = false; ///< Set when shutting down the watchdog
};

View File

@@ -59,7 +59,7 @@ CProcessControl::~CProcessControl()
bool CProcessControl::OnInitialize()
{
// Without monitor no trigger...
m_threadMonitor = std::thread(&CProcessControl::MonitorThread, this);
m_threadMonitor = sdv::core::secure_thread(&CProcessControl::MonitorThread, this);
return true;
}
@@ -76,6 +76,7 @@ void CProcessControl::OnShutdown()
bool CProcessControl::AllowProcessControl() const
{
if (m_bEnableBypass) return true;
const sdv::app::IAppContext* pAppContext = sdv::core::GetCore<sdv::app::IAppContext>();
return pAppContext && (pAppContext->GetContextType() == sdv::app::EAppContext::main ||
pAppContext->GetContextType() == sdv::app::EAppContext::maintenance ||
@@ -574,3 +575,7 @@ void CProcessControl::MonitorThread()
}
}
void CProcessControl::EnableProcessControlAccessBypass()
{
m_bEnableBypass = true;
}

View File

@@ -118,7 +118,12 @@ public:
*/
virtual bool Terminate(/*in*/ sdv::process::TProcessID tProcessID) override;
private:
/**
* @brief Enable process control access (explicitly bypass application check for testing).
*/
void EnableProcessControlAccessBypass();
private:
/**
* @brief Monitor thread function.
*/
@@ -151,12 +156,13 @@ public:
#else
#error OS is not supported!
#endif
mutable std::mutex m_mtxProcesses; ///< Access control for monitor map.
mutable std::mutex m_mtxProcesses; ///< Access control for monitor map.
std::map<sdv::process::TProcessID, std::shared_ptr<SProcessHelper>> m_mapProcesses; ///< Monitor map
uint32_t m_uiNextMonCookie = 1; ///< Next monitor cookie
uint32_t m_uiNextMonCookie = 1; ///< Next monitor cookie
std::map<uint32_t, std::shared_ptr<SProcessHelper>> m_mapMonitors; ///< Map with monitors.
std::atomic_bool m_bShutdown = false; ///< Set to shutdown the monitor thread.
std::thread m_threadMonitor; ///< Monitor thread.
std::atomic_bool m_bShutdown = false; ///< Set to shutdown the monitor thread.
sdv::core::secure_thread m_threadMonitor; ///< Monitor thread.
bool m_bEnableBypass = false; ///< When set, enables process control regardless of application mode.
};
DEFINE_SDV_OBJECT(CProcessControl)

View File

@@ -16,7 +16,7 @@
#include <functional>
CTimer::CTimer(CTaskTimerService& rtimersvc, uint32_t uiPeriod, sdv::core::ITaskExecute* pExecute) :
m_rtimersvc(rtimersvc), m_pExecute(pExecute)
m_rtimersvc(rtimersvc), m_pExecute(pExecute), m_tPermissionTransferID(sdv::core::TransferCurrentPermission())
{
if (!pExecute) return;
@@ -35,6 +35,11 @@ CTimer::CTimer(CTaskTimerService& rtimersvc, uint32_t uiPeriod, sdv::core::ITask
sev.sigev_notify_function = [](sigval sv)
{
CTimer* pTimer = reinterpret_cast<CTimer*>(sv.sival_ptr);
if (pTimer->m_tPermissionTransferID)
{
sdv::core::SetAccessPermission(pTimer->m_tPermissionTransferID);
pTimer->m_tPermissionTransferID = 0u;
}
std::unique_lock<std::mutex> lock(pTimer->m_mtxExecution);
if (!pTimer->m_bRunning) return;
sdv::core::ITaskExecute* pExecuteLocal = reinterpret_cast<sdv::core::ITaskExecute*>(pTimer->m_pExecute);
@@ -111,6 +116,11 @@ void CTimer::ExecuteCallback()
{
if (m_rtimersvc.GetObjectState() != sdv::EObjectState::running) return;
if (!m_pExecute) return;
if (m_tPermissionTransferID)
{
sdv::core::SetAccessPermission(m_tPermissionTransferID);
m_tPermissionTransferID = 0u;
}
if (!m_bPrioritySet)
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
m_bPrioritySet = true;

View File

@@ -102,6 +102,8 @@ private:
std::atomic_bool m_bRunning = false; ///< When set, the timer is running.
std::mutex m_mtxExecution; ///< Prevent killing the timer when in execution.
#endif
sdv::core::TPermissionTransferID m_tPermissionTransferID = 0u; ///< The transfer ID to transfer permissions between the
///< timer creator and the execution thread.
};
/**

View File

@@ -12,20 +12,55 @@ if(UNIX)
# Define project
project(uds_unix_sockets VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(uds_unix_sockets STATIC
# Build sources once and expose them in two forms:
# - uds_unix_sockets: runtime-loadable .sdv module for ModuleControl
# - uds_unix_sockets_static: static link target for unit tests / static consumers
set(UDS_UNIX_SOCKETS_SOURCES
channel_mgnt.cpp
connection.cpp
)
watchdog.cpp
)
target_link_libraries(uds_unix_sockets rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
add_library(uds_unix_sockets SHARED
${UDS_UNIX_SOCKETS_SOURCES}
)
add_library(uds_unix_sockets_static STATIC
${UDS_UNIX_SOCKETS_SOURCES}
)
target_include_directories(uds_unix_sockets
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
./include/
)
target_include_directories(uds_unix_sockets_static
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
./include/
)
target_link_libraries(uds_unix_sockets
PUBLIC
rt
${CMAKE_DL_LIBS}
${CMAKE_THREAD_LIBS_INIT}
)
target_link_libraries(uds_unix_sockets_static
PUBLIC
rt
${CMAKE_DL_LIBS}
${CMAKE_THREAD_LIBS_INIT}
)
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")
# Build dependencies
add_dependencies(uds_unix_sockets CompileCoreIDL)
add_dependencies(uds_unix_sockets_static CompileCoreIDL)
# Appending the service in the service list
set(SDV_Service_List ${SDV_Service_List} uds_unix_sockets PARENT_SCOPE)

View File

@@ -81,19 +81,19 @@ namespace
// Directory selection (/run/user/<uid>/sdv or /tmp/sdv)
std::string CUnixDomainSocketsChannelMgnt::MakeUserRuntimeDir()
{
std::ostringstream oss;
oss << "/run/user/" << ::getuid();
const std::string path = "/run/ipc/sdv";
struct stat st{};
if (::stat(oss.str().c_str(), &st) == 0)
if (::stat(path.c_str(), &st) == 0)
{
std::string path = oss.str() + "/sdv";
::mkdir(path.c_str(), 0770);
return path;
}
::mkdir("/tmp/sdv", 0770);
return "/tmp/sdv";
// fallback if /run/ipc/sdv is not available
const std::string fallback = "/tmp/sdv";
::mkdir(fallback.c_str(), 0770);
return fallback;
}
bool CUnixDomainSocketsChannelMgnt::OnInitialize()
@@ -102,7 +102,11 @@ bool CUnixDomainSocketsChannelMgnt::OnInitialize()
}
void CUnixDomainSocketsChannelMgnt::OnShutdown()
{}
void CUnixDomainSocketsChannelMgnt::OnDestroy()
{
m_watchdog.Clear();
}
// Endpoint creation (server)
@@ -129,33 +133,133 @@ sdv::ipc::SChannelEndpoint CUnixDomainSocketsChannelMgnt::CreateEndpoint(const s
path = ClampSunPath(path);
// Use a shared_ptr and store it to keep the server connection alive
auto server = std::make_shared<CUnixSocketConnection>(-1, true, path);
m_ServerConnections.push_back(server);
// Keep lifetime consistent with shared_mem: watchdog owns active connections.
std::shared_ptr<CUnixSocketConnection> server = std::make_shared<CUnixSocketConnection>(-1, true, path);
// Ignore cppcheck warning; if construction failed, an exception is expected first.
// cppcheck-suppress knownConditionTrueFalse
if (!server)
return {};
server->SetWatchDogRemoveCallback([this](const void* connection)
{
m_watchdog.RemoveConnection(connection);
});
m_watchdog.AddConnection(server);
sdv::ipc::SChannelEndpoint ep{};
ep.pConnection = static_cast<IInterfaceAccess*>(server.get());
ep.ssConnectString = server->GetConnectionString();
// Publish a Provider-wrapped connect string for compatibility with
// CCommunicationControl::CreateClientConnection() flows.
const std::string udsConnectString = server->GetConnectionString();
ep.ssConnectString = std::string("[Provider]\n") +
"Name = \"unix_domain_sockets\"\n" +
"ConnectString = \"" + udsConnectString + "\"\n";
return ep;
}
// Access existing endpoint (server or client)
sdv::IInterfaceAccess* CUnixDomainSocketsChannelMgnt::Access(const sdv::u8string& ssConnectString)
{
const auto kv = ParseKV(static_cast<std::string>(ssConnectString));
const bool isServer = (kv.count("role") && kv.at("role") == "server");
const std::string path = kv.count("path") ? kv.at("path") : (MakeUserRuntimeDir() + "/UDS_auto.sock");
const std::string input = static_cast<std::string>(ssConnectString);
if (isServer)
bool isServer = false;
bool parsed = false;
std::string path;
// Parse structured TOML forms first, but only for non-raw inputs.
// Raw connect strings (proto=uds;...) are intentionally not TOML and
// would produce noisy parser diagnostics like "Missing value".
if (input.rfind("proto=uds", 0) != 0)
{
auto server = std::make_shared<CUnixSocketConnection>(-1, true, path);
m_ServerConnections.push_back(server);
return static_cast<IInterfaceAccess*>(server.get());
sdv::toml::CTOMLParser parser(input);
if (!parser.IsValid())
return nullptr;
const std::string providerName = parser.GetDirect("Provider.Name").GetValue();
if (!providerName.empty())
{
if (providerName != "unix_domain_sockets" &&
providerName != "UnixSocketsChannelControl" &&
providerName != "UnixDomainSocketsChannelControl")
{
return nullptr;
}
const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue();
if (!nested.empty())
{
const auto kv = ParseKV(nested);
if (kv.count("path"))
path = kv.at("path");
}
// Provider descriptions are consumed as client endpoints.
parsed = true;
isServer = false;
}
else
{
// Shared-memory style callers pass [IpcChannel] config directly to Access().
parsed = true;
isServer = false;
}
if (path.empty())
{
auto pathNode = parser.GetDirect("IpcChannel.Path");
if (pathNode.GetType() == sdv::toml::ENodeType::node_string)
{
path = static_cast<std::string>(pathNode.GetValue());
}
else
{
auto nameNode = parser.GetDirect("IpcChannel.Name");
if (nameNode.GetType() == sdv::toml::ENodeType::node_string)
{
const std::string name = static_cast<std::string>(nameNode.GetValue());
if (!name.empty())
path = MakeUserRuntimeDir() + "/" + name + ".sock";
}
}
}
}
// Client: allocated raw pointer (expected to be managed by SDV framework)
auto* client = new CUnixSocketConnection(-1, false, path);
return static_cast<IInterfaceAccess*>(client);
// Raw KV fallback (legacy/tests): proto=uds;role=...;path=...;
if (!parsed)
{
// Only treat as raw format when it actually starts with proto=uds.
if (input.rfind("proto=uds", 0) != 0)
return nullptr;
const auto kv = ParseKV(input);
parsed = true;
isServer = (kv.count("role") && kv.at("role") == "server");
if (kv.count("path"))
path = kv.at("path");
}
if (!parsed)
return nullptr;
if (path.empty())
path = MakeUserRuntimeDir() + "/UDS_auto.sock";
path = ClampSunPath(path);
std::shared_ptr<CUnixSocketConnection> connection =
std::make_shared<CUnixSocketConnection>(-1, isServer, path);
// Ignore cppcheck warning; if construction failed, an exception is expected first.
// cppcheck-suppress knownConditionTrueFalse
if (!connection)
return nullptr;
connection->SetWatchDogRemoveCallback([this](const void* instance)
{
m_watchdog.RemoveConnection(instance);
});
m_watchdog.AddConnection(connection);
return static_cast<IInterfaceAccess*>(connection.get());
}
#endif // defined(__unix__)

View File

@@ -17,6 +17,8 @@
#include <support/component_impl.h>
#include <interfaces/ipc.h>
#include "watchdog.h"
#include <algorithm>
class CUnixSocketConnection;
@@ -37,9 +39,9 @@ public:
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("UnixDomainSocketsChannelControl")
DECLARE_OBJECT_CLASS_ALIAS("LocalChannelControl")
DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl")
DECLARE_OBJECT_CLASS_NAME("UnixSocketsChannelControl")
DECLARE_OBJECT_CLASS_ALIAS("unix_domain_sockets")
DECLARE_DEFAULT_OBJECT_NAME("unix_domain_sockets")
DECLARE_OBJECT_SINGLETON()
/**
@@ -53,6 +55,11 @@ public:
*/
virtual void OnShutdown() override;
/**
* @brief Last function called before destruction. Overload of sdv::CSdvObject::OnDestroy.
*/
virtual void OnDestroy() override;
/**
* @brief Create IPC connection object and return the endpoint information. Overload of
* sdv::ipc::ICreateEndpoint::CreateEndpoint.
@@ -80,8 +87,7 @@ private:
// Helper: choose runtime dir (/run/user/<uid>/sdv) or fallback (/tmp/sdv)
static std::string MakeUserRuntimeDir();
std::vector<std::shared_ptr<CUnixSocketConnection>> m_ServerConnections;
CUnixSocketsConnectionWatchDog m_watchdog;
};
DEFINE_SDV_OBJECT(CUnixDomainSocketsChannelMgnt)

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,8 @@
#include <atomic>
#include <cstdint>
#include <condition_variable>
#include <cstring>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
@@ -98,6 +100,9 @@ public:
/** @brief Destroy object (IObjectDestroy). */
void DestroyObject() override;
/** @brief Register callback used to remove this connection from manager watchdog storage. */
void SetWatchDogRemoveCallback(std::function<void(const void*)> callback);
/** @brief Set state and notify listeners (callback-safe). */
void SetConnectState(sdv::ipc::EConnectState eConnectState);
@@ -153,21 +158,27 @@ public:
SMsgHdr GetMsgHdr() const
{
if (GetSize() < sizeof(SMsgHdr)) return SMsgHdr{0, EMsgType::connect_term};
return *reinterpret_cast<const SMsgHdr*>(GetData());
SMsgHdr hdr{};
std::memcpy(&hdr, GetData(), sizeof(SMsgHdr));
return hdr;
}
/** @return SDV connect header (or default if undersized). */
SConnectMsg GetConnectHdr() const
{
if (GetSize() < sizeof(SConnectMsg)) return SConnectMsg{};
return *reinterpret_cast<const SConnectMsg*>(GetData());
SConnectMsg hdr{};
std::memcpy(&hdr, GetData(), sizeof(SConnectMsg));
return hdr;
}
/** @return SDV fragmented header (or default if undersized). */
SFragmentedMsgHdr GetFragmentedHdr() const
{
if (GetSize() < sizeof(SFragmentedMsgHdr)) return SFragmentedMsgHdr{};
return *reinterpret_cast<const SFragmentedMsgHdr*>(GetData());
SFragmentedMsgHdr hdr{};
std::memcpy(&hdr, GetData(), sizeof(SFragmentedMsgHdr));
return hdr;
}
/** @return true if the SDV envelope is well-formed. */
@@ -253,6 +264,10 @@ public:
*/
void ReceiveMessages();
bool SendConnectMessage(EMsgType type);
bool SendControlMessage(EMsgType type);
/**
* @brief Handle an incoming sync_request message.
*
@@ -360,6 +375,10 @@ public:
*/
void StopThreadsAndCloseSockets(bool unlinkPath);
void WaitForClientEnd();
void CloseClientSocketOnly();
private:
//Transport state
int m_Fd { -1 }; ///< Active connection FD.
@@ -370,19 +389,24 @@ private:
//Threads & control
std::atomic<bool> m_StopReceiveThread { false };
std::atomic<bool> m_StopConnectThread { false };
std::thread m_ReceiveThread;
std::thread m_ConnectThread;
sdv::core::secure_thread m_ReceiveThread;
sdv::core::secure_thread m_ConnectThread;
//State & synchronization
std::condition_variable m_StateCv;
std::atomic<sdv::ipc::EConnectState> m_eConnectState { sdv::ipc::EConnectState::uninitialized };
std::atomic<bool> m_bConnectedOnce { false }; ///< Latched true once a connection was established.
sdv::ipc::IDataReceiveCallback* m_pReceiver { nullptr };
sdv::ipc::IConnectEventCallback* m_pEvent { nullptr };
std::mutex m_MtxConnect;
std::condition_variable m_CvConnect;
std::condition_variable m_cvConnect;
std::mutex m_StateMtx; ///< Protects receiver/event assignment.
std::list<SEventCallback> m_lstEventCallbacks; ///< List containing event callbacks
std::shared_mutex m_mtxEventCallbacks; ///< Protect access to callback list
std::mutex m_WatchdogMtx;
std::function<void(const void*)> m_WatchdogRemoveCallback;
std::atomic<bool> m_DestroyObjectCalled { false };
std::atomic<bool> m_CancelWait { false };
//TX synchronization
std::mutex m_SendMtx;

View File

@@ -0,0 +1,58 @@
/********************************************************************************
* 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 "watchdog.h"
void CUnixSocketsConnectionWatchDog::AddConnectionImpl(const std::shared_ptr<void>& connection)
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_Connections[connection.get()] = connection;
}
void CUnixSocketsConnectionWatchDog::RemoveConnection(const void* connection)
{
if (!connection)
{
return;
}
std::shared_ptr<void> removed;
{
std::lock_guard<std::mutex> lock(m_Mutex);
auto it = m_Connections.find(connection);
if (it == m_Connections.end())
{
return;
}
removed = std::move(it->second);
m_Connections.erase(it);
}
removed.reset();
}
void CUnixSocketsConnectionWatchDog::Clear()
{
std::map<const void*, std::shared_ptr<void>> localConnections;
{
std::lock_guard<std::mutex> lock(m_Mutex);
localConnections.swap(m_Connections);
}
localConnections.clear();
}
#endif // defined(__unix__)

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
*
* Contributors:
* Denisa Ros - initial API and implementation
********************************************************************************/
#if defined(__unix__)
#ifndef UDS_UNIX_SOCKETS_WATCHDOG_H
#define UDS_UNIX_SOCKETS_WATCHDOG_H
#include <map>
#include <memory>
#include <mutex>
class CUnixSocketsConnectionWatchDog
{
public:
template<typename T>
void AddConnection(const std::shared_ptr<T>& connection)
{
if (!connection)
{
return;
}
AddConnectionImpl(std::static_pointer_cast<void>(connection));
}
void RemoveConnection(const void* connection);
void Clear();
private:
void AddConnectionImpl(const std::shared_ptr<void>& connection);
std::mutex m_Mutex;
std::map<const void*, std::shared_ptr<void>> m_Connections;
};
#endif // UDS_UNIX_SOCKETS_WATCHDOG_H
#endif // defined(__unix__)

View File

@@ -12,22 +12,57 @@ if(UNIX)
# Define project
project(uds_unix_tunnel VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(uds_unix_tunnel STATIC
set(UDS_UNIX_TUNNEL_SOURCES
channel_mgnt.cpp
connection.cpp
watchdog.cpp
)
# Define targets
add_library(uds_unix_tunnel STATIC
${UDS_UNIX_TUNNEL_SOURCES}
)
target_link_libraries(uds_unix_tunnel rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
add_library(uds_unix_tunnel_module SHARED
${UDS_UNIX_TUNNEL_SOURCES}
)
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")
target_link_libraries(uds_unix_tunnel
PRIVATE
uds_unix_sockets_static
rt
${CMAKE_DL_LIBS}
${CMAKE_THREAD_LIBS_INIT}
)
target_link_libraries(uds_unix_tunnel_module
PRIVATE
uds_unix_sockets_static
rt
${CMAKE_DL_LIBS}
${CMAKE_THREAD_LIBS_INIT}
)
target_include_directories(uds_unix_tunnel
PRIVATE
./include/
../uds_unix_sockets/
)
target_include_directories(uds_unix_tunnel_module
PRIVATE
./include/
../uds_unix_sockets/
)
set_target_properties(uds_unix_tunnel_module PROPERTIES PREFIX "")
set_target_properties(uds_unix_tunnel_module PROPERTIES SUFFIX ".sdv")
set_target_properties(uds_unix_tunnel_module PROPERTIES OUTPUT_NAME "uds_unix_tunnel")
# Build dependencies
add_dependencies(uds_unix_tunnel CompileCoreIDL)
add_dependencies(uds_unix_tunnel_module CompileCoreIDL)
# Appending the service in the service list
set(SDV_Service_List ${SDV_Service_List} uds_unix_tunnel PARENT_SCOPE)
set(SDV_Service_List ${SDV_Service_List} uds_unix_tunnel_module PARENT_SCOPE)
endif()

View File

@@ -1,3 +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
*
* Contributors:
* Denisa Ros - initial API and implementation
********************************************************************************/
#if defined(__unix__)
#include "channel_mgnt.h"
@@ -15,6 +27,7 @@
namespace
{
static std::atomic<uint32_t> g_nextChannelId{1};
/**
* @brief Parses a semicolon-separated list of key=value pairs into a map.
@@ -52,23 +65,113 @@ static std::string ClampSunPath(const std::string& p)
return (p.size() < MaxLen) ? p : p.substr(0, MaxLen - 1);
}
static std::string GetUserRuntimeDir()
{
const std::string path = "/run/ipc/sdv";
struct stat st{};
if (::stat(path.c_str(), &st) == 0)
{
return path;
}
// fallback if /run/ipc/sdv is not available
const std::string fallback = "/tmp/sdv";
::mkdir(fallback.c_str(), 0770);
return fallback;
}
static std::string BuildTunnelPath(const std::string& baseDir,
const std::string& tunnel,
uint32_t instanceId,
uint32_t pid)
{
std::string dir = baseDir + "/" + tunnel;
if (::mkdir(dir.c_str(), 0770) != 0 && errno != EEXIST)
{
SDV_LOG_WARNING("Failed to create tunnel directory");
}
return dir + "/vapi_" +
std::to_string(instanceId) + "_" +
std::to_string(pid) + ".sock";
}
static bool ExtractTunnelPathFromConfig(const std::string& input, std::string& outPath)
{
const auto kv = ParseKV(input);
if (kv.count("proto"))
{
if (kv.at("proto") != "tunnel")
return false;
auto it = kv.find("path");
outPath = (it != kv.end()) ? it->second : std::string{};
return true;
}
sdv::toml::CTOMLParser parser(input);
if (!parser.IsValid())
return false;
const std::string providerName = parser.GetDirect("Provider.Name").GetValue();
if (!providerName.empty() &&
providerName != "unix_domain_sockets_tunnel" &&
providerName != "UnixTunnelChannelControl" &&
providerName != "WinTunnelChannelControl")
{
return false;
}
const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue();
if (!nested.empty())
{
return ExtractTunnelPathFromConfig(nested, outPath);
}
auto pathNode = parser.GetDirect("IpcChannel.Path");
if (pathNode.GetType() == sdv::toml::ENodeType::node_string)
{
outPath = static_cast<std::string>(pathNode.GetValue());
return true;
}
auto nameNode = parser.GetDirect("IpcChannel.Name");
if (nameNode.GetType() == sdv::toml::ENodeType::node_string)
{
const std::string name = static_cast<std::string>(nameNode.GetValue());
if (!name.empty())
{
outPath = GetUserRuntimeDir() + "/" + name + ".sock";
return true;
}
}
outPath.clear();
return true;
}
static std::string ExtractTunnelName(const std::string& input)
{
const auto kv = ParseKV(input);
if (kv.count("tunnel"))
{
return kv.at("tunnel");
}
// Fallback default
return "default";
}
} // 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";
return GetUserRuntimeDir();
}
bool CUnixTunnelChannelMgnt::OnInitialize()
@@ -77,9 +180,11 @@ bool CUnixTunnelChannelMgnt::OnInitialize()
}
void CUnixTunnelChannelMgnt::OnShutdown()
{}
void CUnixTunnelChannelMgnt::OnDestroy()
{
// Actual cleanup is handled by destructors of CUnixTunnelConnection
// and CUnixSocketConnection (shared_ptr).
m_watchdog.Clear();
}
sdv::ipc::SChannelEndpoint CUnixTunnelChannelMgnt::CreateEndpoint(
@@ -88,91 +193,270 @@ sdv::ipc::SChannelEndpoint CUnixTunnelChannelMgnt::CreateEndpoint(
sdv::ipc::SChannelEndpoint endpoint{};
const std::string baseDir = MakeUserRuntimeDir();
std::string name = "TUNNEL_" + std::to_string(::getpid());
std::string path = baseDir + "/" + name + ".sock";
uint32_t instanceId = 1000;
const sdv::app::IAppContext* pCtx = sdv::core::GetCore<sdv::app::IAppContext>();
if (pCtx && pCtx->GetInstanceID() != 0)
{
instanceId = pCtx->GetInstanceID();
}
// Extract tunnel name from config
std::string input = static_cast<std::string>(ssChannelConfig);
std::string tunnel = ExtractTunnelName(input);
// Build safe path
std::string path = BuildTunnelPath(baseDir, tunnel, instanceId, static_cast<uint32_t>(::getpid()));
// 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());
std::string configuredPath;
auto pathNode = cfg.GetDirect("IpcChannel.Path");
if (pathNode.GetType() == sdv::toml::ENodeType::node_string)
path = static_cast<std::string>(pathNode.GetValue());
if (ExtractTunnelPathFromConfig(input, configuredPath) && !configuredPath.empty())
{
std::string baseName = configuredPath;
auto pos = baseName.find_last_of('/');
if (pos != std::string::npos)
{
baseName = baseName.substr(pos + 1);
}
path = baseDir + "/" + tunnel + "/" + baseName;
SDV_LOG_WARNING("Using custom path without tunnel isolation");
}
else
path = baseDir + "/" + name + ".sock";
{
// fallback - still use tunnel-based path
tunnel = ExtractTunnelName(input);
instanceId = 1000;
if (pCtx && pCtx->GetInstanceID() != 0)
{
instanceId = pCtx->GetInstanceID();
}
path = BuildTunnelPath(baseDir, tunnel, instanceId, static_cast<uint32_t>(::getpid()));
}
}
path = ClampSunPath(path);
// Create underlying UDS server transport
auto udsServer = std::make_shared<CUnixSocketConnection>(
-1,
/*acceptConnectionRequired*/ true,
path);
auto udsServer = std::make_shared<CUnixSocketConnection>(-1, /*acceptConnectionRequired*/ true, path);
uint32_t chId = g_nextChannelId++;
// Create tunnel wrapper on top of UDS
auto tunnelServer = std::make_shared<CUnixTunnelConnection>(
udsServer,
/*channelId*/ 0u);
auto tunnelServer = std::make_shared<CUnixTunnelConnection>(udsServer, /*channelId*/ chId);
m_ServerTunnels.push_back(tunnelServer);
// Ignore cppcheck warning; if construction failed, an exception is expected first.
// cppcheck-suppress knownConditionTrueFalse
if (!tunnelServer)
return {};
tunnelServer->SetWatchDogRemoveCallback([this](const void* connection)
{
m_watchdog.RemoveConnection(connection);
});
m_watchdog.AddConnection(tunnelServer);
endpoint.pConnection = static_cast<sdv::IInterfaceAccess*>(tunnelServer.get());
endpoint.ssConnectString = "proto=tunnel;role=server;path=" + path + ";";
const std::string tunnelConnectString = "proto=tunnel;path=" + path + ";tunnel=" + tunnel + ";";
endpoint.ssConnectString = std::string("[Provider]\n") +
"Name = \"unix_domain_sockets_tunnel\"\n" +
"ConnectString = \"" + tunnelConnectString + "\"\n";
return endpoint;
}
sdv::IInterfaceAccess* CUnixTunnelChannelMgnt::Access(
const sdv::u8string& ssConnectString)
sdv::IInterfaceAccess* CUnixTunnelChannelMgnt::Access(const sdv::u8string& ssConnectString)
{
const auto kv = ParseKV(static_cast<std::string>(ssConnectString));
const std::string input = static_cast<std::string>(ssConnectString);
std::string tunnel = ExtractTunnelName(input);
bool parsed = false;
bool isServer = false;
std::string path;
// Only handle proto=tunnel
if (!kv.count("proto") || kv.at("proto") != "tunnel")
// Parse structured TOML forms first; raw connect strings are handled below.
if (input.rfind("proto=tunnel", 0) != 0)
{
sdv::toml::CTOMLParser parser(input);
if (!parser.IsValid())
{
SDV_LOG_WARNING("[TUNNEL][Access] TOML parse failed. Trying text fallback.");
// Fallback for malformed TOML: try to recover an embedded proto=tunnel connect string.
const auto protoPos = input.find("proto=tunnel");
if (protoPos != std::string::npos)
{
std::string fallbackCs = input.substr(protoPos);
const auto quotePos = fallbackCs.find('"');
if (quotePos != std::string::npos)
fallbackCs = fallbackCs.substr(0, quotePos);
const auto newlinePos = fallbackCs.find('\n');
if (newlinePos != std::string::npos)
fallbackCs = fallbackCs.substr(0, newlinePos);
const auto kv = ParseKV(fallbackCs);
if (kv.count("proto") && kv.at("proto") == "tunnel")
{
parsed = true;
isServer = (kv.count("role") && kv.at("role") == "server");
if (kv.count("path"))
path = kv.at("path");
}
}
if (!parsed)
{
SDV_LOG_WARNING("[TUNNEL][Access] Fallback parse failed. Returning nullptr");
return nullptr;
}
}
if (!parsed)
{
const std::string providerName = parser.GetDirect("Provider.Name").GetValue();
if (!providerName.empty())
{
if (providerName != "unix_domain_sockets_tunnel" &&
providerName != "UnixTunnelChannelControl" &&
providerName != "WinTunnelChannelControl")
{
SDV_LOG_WARNING("[TUNNEL][Access] Unsupported provider. Returning nullptr");
return nullptr;
}
const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue();
if (!nested.empty())
{
const auto nestedKv = ParseKV(nested);
if (nestedKv.count("tunnel"))
{
tunnel = nestedKv.at("tunnel");
}
if (nestedKv.count("proto") && nestedKv.at("proto") != "tunnel")
{
SDV_LOG_WARNING("[TUNNEL][Access] Nested proto is not tunnel. Returning nullptr");
return nullptr;
}
if (nestedKv.count("path"))
{
std::string baseName = nestedKv.at("path");
auto pos = baseName.find_last_of('/');
if (pos != std::string::npos)
{
baseName = baseName.substr(pos + 1);
}
path = MakeUserRuntimeDir() + "/" + tunnel + "/" + baseName;
}
}
parsed = true;
isServer = false;
}
else
{
// Callers can pass [IpcChannel] directly to Access().
parsed = true;
isServer = false;
}
if (path.empty())
{
auto pathNode = parser.GetDirect("IpcChannel.Path");
if (pathNode.GetType() == sdv::toml::ENodeType::node_string)
{
std::string baseName = static_cast<std::string>(pathNode.GetValue());
auto pos = baseName.find_last_of('/');
if (pos != std::string::npos)
{
baseName = baseName.substr(pos + 1);
}
path = MakeUserRuntimeDir() + "/" + tunnel + "/" + baseName;
}
else
{
auto nameNode = parser.GetDirect("IpcChannel.Name");
if (nameNode.GetType() == sdv::toml::ENodeType::node_string)
{
const std::string name = static_cast<std::string>(nameNode.GetValue());
if (!name.empty())
{
path = MakeUserRuntimeDir() + "/" + tunnel + "/" + name + ".sock";
}
}
}
}
}
}
// Raw KV fallback: proto=tunnel;role=...;path=...;
if (!parsed)
{
if (input.rfind("proto=tunnel", 0) != 0)
{
SDV_LOG_WARNING("[TUNNEL][Access] Raw KV parse rejected: input does not start with proto=tunnel. Returning nullptr");
return nullptr;
}
const auto kv = ParseKV(input);
if (!kv.count("proto") || kv.at("proto") != "tunnel")
{
SDV_LOG_WARNING("[TUNNEL][Access] Raw KV parse rejected: missing/invalid proto. Returning nullptr ");
return nullptr;
}
parsed = true;
isServer = (kv.count("role") && kv.at("role") == "server");
if (kv.count("path"))
{
path = kv.at("path");
}
}
if (!parsed)
{
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)
if (path.empty())
{
// 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);
tunnel = ExtractTunnelName(input);
auto tunnelServer = std::make_shared<CUnixTunnelConnection>(
udsServer,
/*channelId*/ 0u);
uint32_t instanceId = 1000;
const sdv::app::IAppContext* pCtx = sdv::core::GetCore<sdv::app::IAppContext>();
if (pCtx && pCtx->GetInstanceID() != 0)
{
instanceId = pCtx->GetInstanceID();
}
m_ServerTunnels.push_back(tunnelServer);
return static_cast<sdv::IInterfaceAccess*>(tunnelServer.get());
path = BuildTunnelPath(MakeUserRuntimeDir(), tunnel, instanceId, static_cast<uint32_t>(::getpid()));
}
// Client: allocate raw pointer (expected to be managed by SDV framework via IObjectDestroy)
auto udsClient = std::make_shared<CUnixSocketConnection>(
-1,
/*acceptConnectionRequired*/ false,
path);
path = ClampSunPath(path);
auto* tunnelClient =
new CUnixTunnelConnection(udsClient, /*channelId*/ 0u);
std::shared_ptr<CUnixSocketConnection> transport = std::make_shared<CUnixSocketConnection>(-1, /*acceptConnectionRequired*/ isServer, path);
uint32_t chId = g_nextChannelId++;
std::shared_ptr<CUnixTunnelConnection> connection = std::make_shared<CUnixTunnelConnection>(transport, /*channelId*/ chId);
return static_cast<sdv::IInterfaceAccess*>(tunnelClient);
// Ignore cppcheck warning; if construction failed, an exception is expected first.
// cppcheck-suppress knownConditionTrueFalse
if (!connection)
return nullptr;
connection->SetWatchDogRemoveCallback([this](const void* instance)
{
m_watchdog.RemoveConnection(instance);
});
m_watchdog.AddConnection(connection);
return static_cast<sdv::IInterfaceAccess*>(connection.get());
}
#endif // defined(__unix__)

View File

@@ -16,43 +16,11 @@
#include <support/component_impl.h>
#include <interfaces/ipc.h>
#include "../sdv_services/uds_unix_sockets/channel_mgnt.h" // existing UDS transport
#include "watchdog.h"
#include <algorithm>
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.
@@ -76,8 +44,8 @@ public:
// 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_CLASS_ALIAS("unix_domain_sockets_tunnel")
DECLARE_DEFAULT_OBJECT_NAME("unix_domain_sockets_tunnel")
DECLARE_OBJECT_SINGLETON()
/**
@@ -96,6 +64,11 @@ public:
*/
virtual void OnShutdown() override;
/**
* @brief Last function called before destruction. Overload of sdv::CSdvObject::OnDestroy.
*/
virtual void OnDestroy() override;
/**
* @brief Creates a tunnel endpoint (server side) and returns endpoint info.
*
@@ -126,12 +99,7 @@ private:
*/
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;
CUnixTunnelConnectionWatchDog m_watchdog;
};
DEFINE_SDV_OBJECT(CUnixTunnelChannelMgnt)

View File

@@ -28,6 +28,12 @@ CUnixTunnelConnection::CUnixTunnelConnection(
// No additional initialization required; acts as a thin wrapper.
}
void CUnixTunnelConnection::SetWatchDogRemoveCallback(std::function<void(const void*)> callback)
{
std::lock_guard<std::mutex> lock(m_WatchdogMtx);
m_WatchdogRemoveCallback = std::move(callback);
}
/**
* @brief Prepends a tunnel header and forwards the data to the underlying transport.
@@ -155,11 +161,38 @@ sdv::ipc::EConnectState CUnixTunnelConnection::GetConnectState() const
void CUnixTunnelConnection::DestroyObject()
{
bool expected = false;
if (!m_DestroyObjectCalled.compare_exchange_strong(expected, true))
{
return;
}
// Stop forwarding to upper layer before transport teardown.
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
m_pUpperReceiver = nullptr;
m_pUpperEvent = nullptr;
}
SetConnectState(sdv::ipc::EConnectState::terminating);
// Disconnect underlying transport and clear callbacks.
Disconnect();
std::lock_guard<std::mutex> lock(m_CallbackMtx);
m_Transport.reset();
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
m_Transport.reset();
}
std::function<void(const void*)> removeCallback;
{
std::lock_guard<std::mutex> watchdogLock(m_WatchdogMtx);
removeCallback = std::move(m_WatchdogRemoveCallback);
}
if (removeCallback)
{
removeCallback(this);
}
}
void CUnixTunnelConnection::ReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)

View File

@@ -20,6 +20,7 @@
#include <atomic>
#include <cstdint>
#include <functional>
#include <mutex>
#include <memory>
#include <thread>
@@ -179,12 +180,20 @@ public:
*/
uint16_t GetChannelId() const noexcept { return m_ChannelId; }
/**
* @brief Register callback used to remove this connection from manager watchdog storage.
*/
void SetWatchDogRemoveCallback(std::function<void(const void*)> callback);
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
std::mutex m_WatchdogMtx;
std::function<void(const void*)> m_WatchdogRemoveCallback;
std::atomic<bool> m_DestroyObjectCalled { false };
};
#endif // UNIX_SOCKET_TUNNEL_CONNECTION_H

View File

@@ -0,0 +1,58 @@
/********************************************************************************
* 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 "watchdog.h"
void CUnixTunnelConnectionWatchDog::AddConnectionImpl(const std::shared_ptr<void>& connection)
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_Connections[connection.get()] = connection;
}
void CUnixTunnelConnectionWatchDog::RemoveConnection(const void* connection)
{
if (!connection)
{
return;
}
std::shared_ptr<void> removed;
{
std::lock_guard<std::mutex> lock(m_Mutex);
auto it = m_Connections.find(connection);
if (it == m_Connections.end())
{
return;
}
removed = std::move(it->second);
m_Connections.erase(it);
}
removed.reset();
}
void CUnixTunnelConnectionWatchDog::Clear()
{
std::map<const void*, std::shared_ptr<void>> localConnections;
{
std::lock_guard<std::mutex> lock(m_Mutex);
localConnections.swap(m_Connections);
}
localConnections.clear();
}
#endif // defined(__unix__)

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
*
* Contributors:
* Denisa Ros - initial API and implementation
********************************************************************************/
#if defined(__unix__)
#ifndef UDS_UNIX_TUNNEL_WATCHDOG_H
#define UDS_UNIX_TUNNEL_WATCHDOG_H
#include <map>
#include <memory>
#include <mutex>
class CUnixTunnelConnectionWatchDog
{
public:
template<typename T>
void AddConnection(const std::shared_ptr<T>& connection)
{
if (!connection)
{
return;
}
AddConnectionImpl(std::static_pointer_cast<void>(connection));
}
void RemoveConnection(const void* connection);
void Clear();
private:
void AddConnectionImpl(const std::shared_ptr<void>& connection);
std::mutex m_Mutex;
std::map<const void*, std::shared_ptr<void>> m_Connections;
};
#endif // UDS_UNIX_TUNNEL_WATCHDOG_H
#endif // defined(__unix__)

View File

@@ -12,31 +12,59 @@ if(WIN32)
# Define project
project(uds_win_sockets VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(uds_win_sockets STATIC
# Build sources once and expose them in two forms:
# - uds_win_sockets: static link target for tests / other local libraries on MinGW
# - uds_win_sockets_module: runtime-loadable .sdv module for ModuleControl
set(UDS_WIN_SOCKETS_SOURCES
channel_mgnt.cpp
connection.cpp
watchdog.cpp
)
target_include_directories(uds_win_sockets
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
./include/
add_library(uds_win_sockets STATIC
${UDS_WIN_SOCKETS_SOURCES}
)
add_library(uds_win_sockets_module SHARED
${UDS_WIN_SOCKETS_SOURCES}
)
target_include_directories(uds_win_sockets
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
./include/
)
target_include_directories(uds_win_sockets_module
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
./include/
)
target_link_libraries(uds_win_sockets
PUBLIC
${CMAKE_THREAD_LIBS_INIT}
${CMAKE_THREAD_LIBS_INIT}
Ws2_32.lib
)
set_target_properties(uds_win_sockets PROPERTIES PREFIX "")
set_target_properties(uds_win_sockets PROPERTIES SUFFIX ".sdv")
target_link_libraries(uds_win_sockets_module
PUBLIC
${CMAKE_THREAD_LIBS_INIT}
Ws2_32.lib
)
set_target_properties(uds_win_sockets_module PROPERTIES
PREFIX ""
SUFFIX ".sdv"
OUTPUT_NAME "uds_win_sockets"
ARCHIVE_OUTPUT_NAME "uds_win_sockets_module"
)
# Build dependencies
add_dependencies(uds_win_sockets CompileCoreIDL)
add_dependencies(uds_win_sockets_module CompileCoreIDL)
# Appending the service in the service list
set(SDV_Service_List ${SDV_Service_List} uds_win_sockets PARENT_SCOPE)
set(SDV_Service_List ${SDV_Service_List} uds_win_sockets_module PARENT_SCOPE)
endif()

View File

@@ -11,15 +11,20 @@
* Denisa Ros - initial API and implementation
********************************************************************************/
#ifdef _WIN32
#include "channel_mgnt.h"
#include "connection.h"
#include "../../global/base64.h"
#include <support/toml.h>
#include <interfaces/process.h>
#include <chrono>
#include <future>
#include <mutex>
#include <thread>
#include <cstring>
#include "../../global/base64.h"
#include <interfaces/app.h>
#include <interfaces/process.h>
#include <support/local_service_access.h>
#include <support/toml.h>
#pragma push_macro("interface")
#undef interface
@@ -44,53 +49,23 @@
namespace
{
/**
* @brief Parse a UDS connect/config string and extract the path
*
* Expected format (substring-based, not strict):
* "proto=uds;path=<something>;"
*
* Behavior:
* - If "proto=uds" is missing -> returns false (not a UDS config)
* - If "path=" is missing -> returns true and outPath is cleared
* - If "path=" is present -> extracts the substring until ';' or end
*
* @param cs Input configuration / connect string
* @param outPath Output: extracted path (possibly empty)
* @return true if this looks like a UDS string, false otherwise
*/
static bool ParseUdsPath(const std::string& cs, std::string& outPath)
static bool EnsureWSAInitialized()
{
constexpr const char* protoKey = "proto=uds";
constexpr const char* pathKey = "path=";
static std::once_flag s_once;
static bool s_ok = false;
// Must contain "proto=uds" to be considered UDS
if (cs.find(protoKey) == std::string::npos)
std::call_once(s_once, []()
{
return false;
}
WSADATA wsa{};
const int rc = WSAStartup(MAKEWORD(2, 2), &wsa);
s_ok = (rc == 0);
if (!s_ok)
{
SDV_LOG_ERROR("[AF_UNIX] WSAStartup failed, rc=", rc);
}
});
const auto p = cs.find(pathKey);
if (p == std::string::npos)
{
// No path given, but proto=uds is present -> use default later
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;
return s_ok;
}
/**
@@ -105,6 +80,7 @@ static bool ParseUdsPath(const std::string& cs, std::string& outPath)
*
* @return Expanded string, or the original input on failure
*/
static std::string ExpandEnvVars(const std::string& in)
{
if (in.find('%') == std::string::npos)
@@ -112,9 +88,8 @@ static std::string ExpandEnvVars(const std::string& in)
return in;
}
char buf[4096] = {};
DWORD n = ExpandEnvironmentStringsA(in.c_str(), buf, static_cast<DWORD>(sizeof(buf)));
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);
@@ -135,19 +110,181 @@ static std::string ExpandEnvVars(const std::string& in)
*
* @return A safe pathname guaranteed to fit into sun_path
*/
static std::string ClampUdsPath(const std::string& p)
{
SOCKADDR_UN tmp{};
SOCKADDR_UN tmp{};
constexpr auto kMax = sizeof(tmp.sun_path) - 1;
if (p.size() <= kMax)
{
return p;
}
return p.substr(0, kMax);
}
/*static std::string BuildFinalUdsPath(const std::string& rawPath)
{
std::string full = ExpandEnvVars(rawPath);
auto pos = full.find_last_of("\\/");
if (pos != std::string::npos)
{
const std::string dir = full.substr(0, pos);
CreateDirectoryA(dir.c_str(), nullptr);
}
return ClampUdsPath(full);
}*/
/**
* @brief Parse a UDS connect/config string and extract the path
*
* Expected format (substring-based, not strict):
* "proto=uds;path=<something>;"
*
* Behavior:
* - If "proto=uds" is missing -> returns false (not a UDS config)
* - If "path=" is missing -> returns true and outPath is cleared
* - If "path=" is present -> extracts the substring until ';' or end
*
* @param cs Input configuration / connect string
* @param outPath Output: extracted path (possibly empty)
* @return true if this looks like a UDS string, false otherwise
*/
static bool ParseUdsPath(const std::string& cs, std::string& outPath)
{
constexpr const char* protoKey = "proto=uds";
constexpr const char* pathKey = "path=";
// Strict raw connect string only
if (cs.rfind(protoKey, 0) != 0)
{
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;
}
static std::string SanitizeUdsName(std::string name)
{
for (char& ch : name)
{
const bool isAlphaNum = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9');
if (!isAlphaNum && ch != '_' && ch != '-')
{
ch = '_';
}
}
if (name.empty())
{
name = "sdv";
}
return name;
}
/**
* @brief Build a named UDS raw path using the channel name
*
* @param[in] channelName The name of the channel
*
* @return A raw UDS path suitable for further processing
*/
static std::string BuildNamedUdsRawPath(const std::string& channelName)
{
return "%LOCALAPPDATA%/sdv/" + SanitizeUdsName(channelName) + ".sock";
}
/**
* @brief Extract a UDS connect string from either raw UDS format or Provider TOML.
*
* Supported inputs:
* - "proto=uds;path=<...>;"
* - [Provider]\nName="unix_domain_sockets"\nConnectString="proto=uds;path=<...>;"
*/
static bool ExtractUdsConnectString(const std::string& in,
std::string& outUdsConnectString)
{
std::string path;
// Case 1: strict raw UDS connect string
if (ParseUdsPath(in, path))
{
outUdsConnectString = in;
return true;
}
// Do not run the TOML parser on obviously non-TOML garbage
//if (!LooksLikeToml(in))
//{
// return false;
//}
// Case 2: structured TOML
sdv::toml::CTOMLParser parser(in);
if (!parser.IsValid())
{
return false;
}
const std::string providerName = parser.GetDirect("Provider.Name").GetValue();
if (!providerName.empty() &&
providerName != "unix_domain_sockets" &&
providerName != "WinSocketsChannelControl")
{
return false;
}
const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue();
if (!nested.empty())
{
if (ParseUdsPath(nested, path))
{
outUdsConnectString = nested;
return true;
}
return false;
}
const std::string cfgPath = parser.GetDirect("IpcChannel.Path").GetValue();
if (!cfgPath.empty())
{
outUdsConnectString = "proto=uds;path=" + cfgPath + ";";
return true;
}
const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue();
if (!cfgName.empty())
{
outUdsConnectString = "proto=uds;path=" + BuildNamedUdsRawPath(cfgName) + ";";
return true;
}
return false;
}
/**
* @brief Normalize a UDS path for display/logging purposes
*
@@ -166,20 +303,18 @@ static std::string ClampUdsPath(const std::string& p)
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);
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.sock";
}
SDV_LOG_INFO("[AF_UNIX] Normalize raw='", raw, "' -> base='", base, "'");
return ClampUdsPath(base);
}
/**
* @brief Build a short absolute Win32 path suitable for AF_UNIX `sun_path`
*
@@ -224,6 +359,41 @@ static std::string MakeShortWinUdsPath(const std::string& raw)
return ClampUdsPath(full);
}
/**
* @brief Build the default UDS path using the current SDV instance ID.
*
* Using a single global socket filename makes independently running test
* processes trample each other when the build executes multiple post-build
* tests in parallel. Namespace the implicit endpoint per instance instead.
*/
static std::string GetDefaultUdsRawPath()
{
uint32_t instanceId = 1000u;
const sdv::app::IAppContext* pAppContext = sdv::core::GetCore<sdv::app::IAppContext>();
if (pAppContext && pAppContext->GetInstanceID() != 0u)
{
instanceId = pAppContext->GetInstanceID();
}
return "%LOCALAPPDATA%/sdv/vapi_" + std::to_string(instanceId) + ".sock";
}
static std::string GetUniqueEndpointUdsRawPath()
{
static std::atomic<uint32_t> nextEndpointId { 0u };
uint32_t instanceId = 1000u;
const sdv::app::IAppContext* pAppContext = sdv::core::GetCore<sdv::app::IAppContext>();
if (pAppContext && pAppContext->GetInstanceID() != 0u)
{
instanceId = pAppContext->GetInstanceID();
}
const uint32_t endpointId = nextEndpointId.fetch_add(1u, std::memory_order_relaxed);
return "%LOCALAPPDATA%/sdv/vapi_" + std::to_string(instanceId) + "_" +
std::to_string(static_cast<uint32_t>(GetCurrentProcessId())) + "_" + std::to_string(endpointId) + ".sock";
}
/**
* @brief Create a listening AF_UNIX socket on Windows
*
@@ -239,6 +409,9 @@ static std::string MakeShortWinUdsPath(const std::string& raw)
*/
static SOCKET CreateUnixListenSocket(const std::string& rawPath)
{
if (!EnsureWSAInitialized())
return INVALID_SOCKET;
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
@@ -285,118 +458,99 @@ static SOCKET CreateUnixListenSocket(const std::string& rawPath)
return s;
}
/**
* @brief Connect to a Windows AF_UNIX server socket with retry logic
*
* Repeatedly attempts to connect to the server's UDS path until either:
* - connection succeeds, or
* - total timeout is exceeded
*
* On each attempt:
* - a new socket() is created
* - connect() is attempted
* - on failure the socket is closed and retried
*
* This mirrors Linux AF_UNIX behavior where the client waits for the
* server's socket file to appear/become ready
*
* @param[in] rawPath Raw UDS path from configuration
* @param[in] totalTimeoutMs Maximum total wait time in milliseconds
* @param[in] retryDelayMs Delay between retries in milliseconds
*
* @return Connected SOCKET on success, INVALID_SOCKET on timeout or error
*/
static SOCKET ConnectUnixSocket(
const std::string& rawPath,
uint32_t totalTimeoutMs,
uint32_t retryDelayMs)
/*static SOCKET ConnectUnixSocket(const std::string& rawPath,
uint32_t totalTimeoutMs,
uint32_t retryDelayMs)
{
const std::string udsPath = MakeShortWinUdsPath(rawPath);
if (!EnsureWSAInitialized())
{
return INVALID_SOCKET;
}
const std::string udsPath = BuildFinalUdsPath(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
);
offsetof(SOCKADDR_UN, sun_path) + std::strlen(addr.sun_path) + 1);
const auto deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(totalTimeoutMs);
auto deadline =
std::chrono::steady_clock::now() + std::chrono::milliseconds(totalTimeoutMs);
while (true)
{
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
SDV_LOG_ERROR("[AF_UNIX] socket() FAIL (client), WSA=", WSAGetLastError());
SDV_LOG_ERROR("[AF_UNIX] socket FAIL (client), WSA=", WSAGetLastError());
return INVALID_SOCKET;
}
if (connect(s, reinterpret_cast<const sockaddr*>(&addr), addrlen) == 0)
{
SDV_LOG_INFO("[AF_UNIX] connect OK (pathname), path='", udsPath, "'");
SDV_LOG_INFO("[AF_UNIX] connect OK: ", udsPath);
return s;
}
int lastError = WSAGetLastError();
const int err = WSAGetLastError();
closesocket(s);
if (std::chrono::steady_clock::now() >= deadline)
{
SDV_LOG_ERROR(
"[AF_UNIX] connect TIMEOUT (pathname), last WSA=",
lastError, ", path='", udsPath, "'"
);
SDV_LOG_ERROR("[AF_UNIX] connect TIMEOUT, WSA=", err, ", path=", udsPath);
return INVALID_SOCKET;
}
std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
}
}
}*/
} // anonymous namespace
bool CSocketsChannelMgnt::OnInitialize()
{
return true;
}
void CSocketsChannelMgnt::OnServerClosed(const std::string& udsPath, CWinsockConnection* ptr)
{
std::lock_guard<std::mutex> lock(m_udsMtx);
auto it = m_udsServers.find(udsPath);
if (it != m_udsServers.end() && it->second.get() == ptr)
{
// Remove the server entry only if it matches the pointer we know
m_udsServers.erase(it);
}
// Mark this UDS path as no longer claimed
m_udsServerClaimed.erase(udsPath);
return EnsureWSAInitialized();
}
void CSocketsChannelMgnt::OnShutdown()
{}
{
}
void CSocketsChannelMgnt::OnDestroy()
{
m_watchdog.Clear();
}
sdv::ipc::SChannelEndpoint CSocketsChannelMgnt::CreateEndpoint(const sdv::u8string& cfgStr)
{
// Ensure WinSock is initialized on Windows
if (StartUpWinSock() != 0)
{
// If WinSock cannot be initialized, we cannot create an endpoint
return {};
}
// Parse UDS path from config. If proto!=uds, we still default to UDS
std::string udsRaw;
bool udsRequested = ParseUdsPath(cfgStr, udsRaw);
if (!udsRequested || udsRaw.empty())
{
// Default path if not provided or not UDS-specific
udsRaw = "%LOCALAPPDATA%/sdv/vapi.sock";
sdv::toml::CTOMLParser parser(cfgStr);
const std::string cfgPath = parser.GetDirect("IpcChannel.Path").GetValue();
const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue();
if (!cfgPath.empty())
{
udsRaw = cfgPath;
}
else if (!cfgName.empty())
{
udsRaw = BuildNamedUdsRawPath(cfgName);
}
else if (cfgStr.empty())
{
udsRaw = GetUniqueEndpointUdsRawPath();
}
else
{
udsRaw = GetDefaultUdsRawPath();
}
}
std::string udsPath = NormalizeUdsPathForWindows(udsRaw);
@@ -411,75 +565,81 @@ sdv::ipc::SChannelEndpoint CSocketsChannelMgnt::CreateEndpoint(const sdv::u8stri
// Server-side CWinsockConnection, it will accept() a client on first use
auto server = std::make_shared<CWinsockConnection>(listenSocket, /*acceptRequired*/ true);
// Ignore cppcheck warning; if construction failed, an exception is expected first.
// cppcheck-suppress knownConditionTrueFalse
if (!server)
{
std::lock_guard<std::mutex> lock(m_udsMtx);
m_udsServers[udsPath] = server;
m_udsServerClaimed.erase(udsPath);
return {};
}
server->SetWatchDogRemoveCallback([this](const void* connection)
{
m_watchdog.RemoveConnection(connection);
});
m_watchdog.AddConnection(server);
sdv::ipc::SChannelEndpoint ep{};
ep.pConnection = static_cast<IInterfaceAccess*>(server.get());
ep.ssConnectString = "proto=uds;path=" + udsPath + ";";
ep.pConnection = static_cast<IInterfaceAccess*>(server.get());
// Keep compatibility with CCommunicationControl::CreateClientConnection,
// which expects a Provider TOML section with Provider.Name.
const std::string udsConnectString = "proto=uds;path=" + udsPath + ";";
ep.ssConnectString = udsConnectString;
return ep;
}
sdv::IInterfaceAccess* CSocketsChannelMgnt::Access(const sdv::u8string& cs)
{
// Ensure WinSock is initialized
if (StartUpWinSock() != 0)
std::string udsConnectString;
if (!ExtractUdsConnectString(cs, udsConnectString))
{
// Not a UDS connect string / provider description
return nullptr;
}
std::string udsRaw;
if (!ParseUdsPath(cs, udsRaw))
{
// Not a UDS connect string
return nullptr;
}
ParseUdsPath(udsConnectString, udsRaw);
if (udsRaw.empty())
{
udsRaw = "%LOCALAPPDATA%/sdv/vapi.sock";
udsRaw = GetDefaultUdsRawPath();
}
std::string udsPath = NormalizeUdsPathForWindows(udsRaw);
SDV_LOG_INFO("[AF_UNIX] Access udsPath=", udsPath);
const bool isServer = (udsConnectString.find("role=server") != std::string::npos);
std::shared_ptr<CWinsockConnection> connection;
if (isServer)
{
std::lock_guard<std::mutex> lock(m_udsMtx);
auto it = m_udsServers.find(udsPath);
if (it != m_udsServers.end() && it->second != nullptr)
SOCKET listenSocket = CreateUnixListenSocket(udsPath);
if (listenSocket == INVALID_SOCKET)
{
// Return the server-side object only once for this UDS path
if (!m_udsServerClaimed.count(udsPath))
{
m_udsServerClaimed.insert(udsPath);
SDV_LOG_INFO("[AF_UNIX] Access -> RETURN SERVER for ", udsPath);
return it->second.get(); // server object (acceptRequired=true)
}
// Otherwise, later calls will create a client socket below
return nullptr;
}
connection = std::make_shared<CWinsockConnection>(listenSocket, /*acceptRequired*/ true);
}
else
{
// Client-side endpoint object. The actual socket connect is deferred to AsyncConnect,
// matching the semantic contract used by ipc_com and shared memory.
connection = std::make_shared<CWinsockConnection>(udsPath);
}
// CLIENT: create a socket connected to the same udsPath
SOCKET s = ConnectUnixSocket(udsPath,
/*totalTimeoutMs*/ 5000,
/*retryDelayMs*/ 50);
if (s == INVALID_SOCKET)
// Ignore cppcheck warning; if construction failed, an exception is expected first.
// cppcheck-suppress knownConditionTrueFalse
if (!connection)
{
return nullptr;
}
SDV_LOG_INFO("[AF_UNIX] Access -> CREATE CLIENT for ", udsPath);
connection->SetWatchDogRemoveCallback([this](const void* instance)
{
m_watchdog.RemoveConnection(instance);
});
// Client-side connection object (acceptRequired=false)
// Ownership is transferred to the caller (VAPI runtime)
return new CWinsockConnection(s, /*acceptRequired*/ false);
m_watchdog.AddConnection(connection);
return static_cast<IInterfaceAccess*>(connection.get());
}
#endif

View File

@@ -18,16 +18,14 @@
#include <support/component_impl.h>
#include <interfaces/ipc.h>
#include "connection.h"
#include "watchdog.h"
#include <algorithm>
#include <map>
#include <unordered_set>
#include <mutex>
#include <memory>
#include <string>
#ifdef _WIN32
// Winsock headers are required for SOCKET / AF_UNIX / WSAStartup
// NOTE: The actual initialization is done via StartUpWinSock()
# include <ws2tcpip.h>
#endif
@@ -77,40 +75,6 @@ struct CAddrInfo
addrinfo* AddressInfo { nullptr };
};
/**
* @brief Initialize WinSock on Windows (idempotent)
*
* This helper ensures WSAStartup() is called only once in the process
*
* @return 0 on success, otherwise a WinSock error code
*/
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
}
/**
* @brief Simple pair of sockets used to connect two child processes
*
@@ -147,8 +111,8 @@ public:
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("WinSocketsChannelControl")
DECLARE_OBJECT_CLASS_ALIAS("LocalChannelControl")
DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl")
DECLARE_OBJECT_CLASS_ALIAS("unix_domain_sockets")
DECLARE_DEFAULT_OBJECT_NAME("unix_domain_sockets")
DECLARE_OBJECT_SINGLETON()
virtual ~CSocketsChannelMgnt() = default;
@@ -164,6 +128,11 @@ public:
*/
virtual void OnShutdown() override;
/**
* @brief Last function called before destruction. Overload of sdv::CSdvObject::OnDestroy.
*/
virtual void OnDestroy() override;
/**
* @brief Create an IPC endpoint and return its connection information
*
@@ -208,29 +177,8 @@ public:
*/
sdv::IInterfaceAccess* Access(const sdv::u8string& ssConnectString) override;
/**
* @brief Called by a CWinsockConnection instance when the server side is closed
*
* Used to clean up internal registries for a given UDS path
*
* @param ptr Pointer to the CWinsockConnection instance that was closed
*/
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<CWinsockConnection>> m_udsServers;
/**
* @brief Set of UDS paths that already returned their server-side
* connection once via Access()
*
* This prevents returning the same server object multiple times
*/
std::unordered_set<std::string> m_udsServerClaimed;
/// @brief Mutex protecting m_udsServers and m_udsServerClaimed
std::mutex m_udsMtx;
CWinSocketsConnectionWatchDog m_watchdog;
};
// SDV object factory macro

View File

@@ -17,11 +17,92 @@
#include <WS2tcpip.h>
#include <cstring>
#include <chrono>
#include <afunix.h>
namespace
{
static std::string ExpandEnvVarsLocal(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;
}
static std::string ClampUdsPathLocal(const std::string& path)
{
sockaddr_un tmp{};
constexpr auto kMax = sizeof(tmp.sun_path) - 1;
return path.size() <= kMax ? path : path.substr(0, kMax);
}
static std::string MakeShortWinUdsPathLocal(const std::string& raw)
{
std::string path = ExpandEnvVarsLocal(raw);
const size_t pos = path.find_last_of("/\\");
std::string base = (pos == std::string::npos) ? path : path.substr(pos + 1);
if (base.empty())
{
base = "sdv.sock";
}
std::string dir = ExpandEnvVarsLocal("%TEMP%\\sdv\\");
CreateDirectoryA(dir.c_str(), nullptr);
return ClampUdsPathLocal(dir + base);
}
static SOCKET ConnectUnixSocketLocal(const std::string& rawPath, uint32_t totalTimeoutMs, uint32_t retryDelayMs)
{
const std::string udsPath = MakeShortWinUdsPathLocal(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);
while (true)
{
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
return INVALID_SOCKET;
}
if (connect(s, reinterpret_cast<const sockaddr*>(&addr), addrlen) == 0)
{
return s;
}
const int lastError = WSAGetLastError();
closesocket(s);
if (std::chrono::steady_clock::now() >= deadline)
{
SDV_LOG_ERROR("[AF_UNIX] lazy connect TIMEOUT, last WSA=", lastError, ", path='", udsPath, "'");
return INVALID_SOCKET;
}
std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
}
}
}
CWinsockConnection::CWinsockConnection(unsigned long long preconfiguredSocket, bool acceptConnectionRequired)
: m_ConnectionState(sdv::ipc::EConnectState::uninitialized)
, m_AcceptConnectionRequired(acceptConnectionRequired)
, m_CancelWait(false)
, m_UdsPath()
{
// Initialize legacy buffers with zero (kept for potential compatibility)
std::fill(std::begin(m_SendBuffer), std::end(m_SendBuffer), '\0');
@@ -41,6 +122,17 @@ CWinsockConnection::CWinsockConnection(unsigned long long preconfiguredSocket, b
}
}
CWinsockConnection::CWinsockConnection(const std::string& udsPath)
: m_ConnectionState(sdv::ipc::EConnectState::uninitialized)
, m_AcceptConnectionRequired(false)
, m_UdsPath(udsPath)
{
std::fill(std::begin(m_SendBuffer), std::end(m_SendBuffer), '\0');
std::fill(std::begin(m_ReceiveBuffer), std::end(m_ReceiveBuffer), '\0');
m_ListenSocket = INVALID_SOCKET;
m_ConnectionSocket = INVALID_SOCKET;
}
/*CWinsockConnection::CWinsockConnection()
: m_ConnectionState(sdv::ipc::EConnectState::uninitialized)
, m_AcceptConnectionRequired(false)
@@ -65,28 +157,47 @@ CWinsockConnection::~CWinsockConnection()
}
}
void CWinsockConnection::SetWatchDogRemoveCallback(std::function<void(const void*)> callback)
{
std::lock_guard<std::mutex> lock(m_WatchdogMtx);
m_WatchdogRemoveCallback = std::move(callback);
}
void CWinsockConnection::SetConnectState(sdv::ipc::EConnectState state)
{
sdv::ipc::IConnectEventCallback* pEventLocal = nullptr;
{
std::lock_guard<std::mutex> lk(m_MtxConnect);
m_ConnectionState.store(state, std::memory_order_release);
// Main receiver has priority over registered state listener
if (m_pMainEvent)
{
pEventLocal = m_pMainEvent;
}
else
{
pEventLocal = m_pRegisteredEvent;
}
}
// Wake up any waiter
m_CvConnect.notify_all();
// Notify event callback if registered
if (m_pEvent)
if (pEventLocal)
{
try
{
m_pEvent->SetConnectState(state);
pEventLocal->SetConnectState(state);
}
catch (...)
{
// Ignore callbacks throwing exceptions
}
}
}
int32_t CWinsockConnection::Send(const char* data, int32_t dataLength)
@@ -177,7 +288,7 @@ bool CWinsockConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>
(kMaxUdsPacketSize - static_cast<uint32_t>(sizeof(SFragmentedMsgHdr))) :
0;
if (maxPayloadFrag == 0)
if constexpr (maxPayloadFrag == 0)
{
return false;
}
@@ -318,9 +429,18 @@ SOCKET CWinsockConnection::AcceptConnection()
tv.tv_usec = 50 * 1000; // 50 ms
const int sr = ::select(0, &rfds, nullptr, nullptr, &tv);
if (sr == SOCKET_ERROR)
{
SDV_LOG_ERROR("[AF_UNIX] select(listen) FAIL, WSA=", WSAGetLastError());
const int err = WSAGetLastError();
if (m_StopConnectThread.load() || m_ListenSocket == INVALID_SOCKET)
{
SDV_LOG_INFO("[AF_UNIX] select(listen) canceled during shutdown, WSA=", err);
return INVALID_SOCKET;
}
SDV_LOG_ERROR("[AF_UNIX] select(listen) FAIL, WSA=", err);
SetConnectState(sdv::ipc::EConnectState::connection_error);
return INVALID_SOCKET;
}
@@ -331,9 +451,17 @@ SOCKET CWinsockConnection::AcceptConnection()
}
SOCKET c = ::accept(m_ListenSocket, nullptr, nullptr);
if (c == INVALID_SOCKET)
{
const int err = WSAGetLastError();
if (m_StopConnectThread.load() || m_ListenSocket == INVALID_SOCKET || err == WSAENOTSOCK)
{
SDV_LOG_INFO("[AF_UNIX] accept canceled during shutdown, WSA=", err);
return INVALID_SOCKET;
}
if (err == WSAEINTR || err == WSAEWOULDBLOCK)
{
continue;
@@ -355,23 +483,53 @@ SOCKET CWinsockConnection::AcceptConnection()
bool CWinsockConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
{
auto expectedState = m_ConnectionState.load(std::memory_order_acquire);
while (true)
{
if (expectedState == sdv::ipc::EConnectState::connected)
{
return true;
}
if (expectedState == sdv::ipc::EConnectState::initializing)
{
SDV_LOG_WARNING("[AF_UNIX] AsyncConnect ignored: connect worker already running");
return false;
}
if (m_ConnectionState.compare_exchange_weak(
expectedState,
sdv::ipc::EConnectState::initializing,
std::memory_order_acq_rel,
std::memory_order_acquire))
{
break;
}
}
// Store callbacks
m_pReceiver = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IDataReceiveCallback>();
m_pEvent = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IConnectEventCallback>();
auto* pReceiverIfc = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IDataReceiveCallback>();
auto* pEventIfc = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IConnectEventCallback>();
{
std::lock_guard<std::mutex> lk(m_MtxConnect);
m_pReceiver = pReceiverIfc;
m_pMainEvent = pEventIfc;
}
// Reset stop flags
m_StopReceiveThread.store(false);
m_StopConnectThread.store(false);
m_CancelWait.store(false);
// Join old threads if any
// Join old workers only after they completed.
// Joining an active worker here can block forever when a previous connect is still pending.
if (m_ReceiveThread.joinable())
m_ReceiveThread.join();
if (m_ConnectThread.joinable())
m_ConnectThread.join();
// Start the connect worker
m_ConnectThread = std::thread(&CWinsockConnection::ConnectWorker, this);
m_ConnectThread = sdv::core::secure_thread(&CWinsockConnection::ConnectWorker, this);
return true;
}
@@ -428,11 +586,16 @@ void CWinsockConnection::Disconnect()
uint64_t CWinsockConnection::RegisterStateEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback)
{
auto* pEventIfc = sdv::TInterfaceAccessPtr(pEventCallback).GetInterface<sdv::ipc::IConnectEventCallback>();
// Extract IConnectEventCallback interface
m_pEvent = sdv::TInterfaceAccessPtr(pEventCallback).GetInterface<sdv::ipc::IConnectEventCallback>();
{
std::lock_guard<std::mutex> lk(m_MtxConnect);
m_pRegisteredEvent = pEventIfc;
}
// Only one callback is supported; cookie 1 = valid
return (m_pEvent != nullptr) ? 1ULL : 0ULL;
return (pEventIfc != nullptr) ? 1ULL : 0ULL;
}
void CWinsockConnection::UnregisterStateEventCallback(/*in*/ uint64_t uiCookie)
@@ -440,7 +603,9 @@ void CWinsockConnection::UnregisterStateEventCallback(/*in*/ uint64_t uiCookie)
// Only one callback supported -> cookie value is 1
if (uiCookie == 1ULL)
{
m_pEvent = nullptr;
std::lock_guard<std::mutex> lk(m_MtxConnect);
m_pMainEvent = nullptr;
m_pRegisteredEvent = nullptr;
}
}
@@ -451,11 +616,38 @@ sdv::ipc::EConnectState CWinsockConnection::GetConnectState() const
void CWinsockConnection::DestroyObject()
{
m_StopReceiveThread = true;
m_StopConnectThread = true;
StopThreadsAndCloseSockets();
m_ConnectionState = sdv::ipc::EConnectState::disconnected;
bool expected = false;
if (!m_DestroyObjectCalled.compare_exchange_strong(expected, true))
{
return;
}
m_StopReceiveThread.store(true);
m_StopConnectThread.store(true);
// Optional: notify terminating before releasing callbacks
SetConnectState(sdv::ipc::EConnectState::terminating);
Disconnect();
// Release callbacks only during final teardown
{
std::lock_guard<std::mutex> lk(m_MtxConnect);
m_pReceiver = nullptr;
m_pMainEvent = nullptr;
m_pRegisteredEvent = nullptr;
}
std::function<void(const void*)> removeCallback;
{
std::lock_guard<std::mutex> lock(m_WatchdogMtx);
removeCallback = std::move(m_WatchdogRemoveCallback);
}
if (removeCallback)
{
removeCallback(this);
}
}
void CWinsockConnection::ConnectWorker()
@@ -482,16 +674,6 @@ void CWinsockConnection::ConnectWorker()
if (c == INVALID_SOCKET)
{
if (m_pEvent)
{
try
{
m_pEvent->SetConnectState(m_ConnectionState);
}
catch (...)
{
}
}
return;
}
@@ -505,8 +687,22 @@ void CWinsockConnection::ConnectWorker()
// CLIENT SIDE
if (m_ConnectionSocket == INVALID_SOCKET)
{
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
SetConnectState(sdv::ipc::EConnectState::initializing);
if (m_UdsPath.empty())
{
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
SOCKET s = ConnectUnixSocketLocal(m_UdsPath, 5000, 50);
if (s == INVALID_SOCKET)
{
SetConnectState(sdv::ipc::EConnectState::connection_error);
return;
}
m_ConnectionSocket = s;
}
}
@@ -528,7 +724,7 @@ void CWinsockConnection::StartReceiveThread_Unsafe()
}
m_StopReceiveThread.store(false);
m_ReceiveThread = std::thread(&CWinsockConnection::ReceiveMessages, this);
m_ReceiveThread = sdv::core::secure_thread(&CWinsockConnection::ReceiveMessages, this);
}
void CWinsockConnection::StopThreadsAndCloseSockets()
@@ -583,6 +779,7 @@ void CWinsockConnection::StopThreadsAndCloseSockets()
SDV_LOG_INFO("[AF_UNIX] StopThreadsAndCloseSockets: closing listen=%llu conn=%llu",
static_cast<uint64_t>(l),
static_cast<uint64_t>(s));
}
bool CWinsockConnection::ReadNumberOfBytes(char* buffer, uint32_t length)
@@ -608,7 +805,7 @@ bool CWinsockConnection::ReadNumberOfBytes(char* buffer, uint32_t length)
continue;
}
SDV_LOG_WARNING("[UDS][RX] recv() error: ", std::strerror(err));
SDV_LOG_WARNING("[UDS][RX] recv() FAIL, WSA=", err);
return false;
}
@@ -742,8 +939,13 @@ bool CWinsockConnection::ReadDataChunk(const CMessage& message, uint32_t offset,
#if ENABLE_DECOUPLING > 0
// optional queueing path...
#else
if (m_pReceiver)
m_pReceiver->ReceiveData(dataCtx.seqDataChunks);
sdv::ipc::IDataReceiveCallback* pReceiverLocal = nullptr;
{
std::lock_guard<std::mutex> lk(m_MtxConnect);
pReceiverLocal = m_pReceiver;
}
if (pReceiverLocal)
pReceiverLocal->ReceiveData(dataCtx.seqDataChunks);
dataCtx = SDataContext(); // reset context
#endif
break;

View File

@@ -27,6 +27,8 @@
#include <vector>
#include <cstdint>
#include <cstddef>
#include <functional>
#include <string>
#ifdef _WIN32
# include <WinSock2.h>
@@ -70,11 +72,12 @@ struct SMsgHeader
* - sdv::ipc::IConnect : async connect / wait / state / events
* - sdv::IObjectDestroy : explicit destruction hook for SDV runtime
*/
class CWinsockConnection
: public sdv::IInterfaceAccess
, public sdv::ipc::IDataSend
, public sdv::ipc::IConnect
, public sdv::IObjectDestroy
class CWinsockConnection :
//public sdv::CSharedLifetimeControlImpl<CWinsockConnection>, public sdv::ipc::IDataSend, public sdv::ipc::IConnect
public sdv::IInterfaceAccess,
public sdv::ipc::IDataSend,
public sdv::ipc::IConnect,
public sdv::IObjectDestroy
{
public:
/**
@@ -89,6 +92,12 @@ public:
*/
CWinsockConnection(unsigned long long preconfiguredSocket, bool acceptConnectionRequired);
/**
* @brief Create a client endpoint that connects lazily in AsyncConnect.
* @param[in] udsPath Normalized path for AF_UNIX connection.
*/
explicit CWinsockConnection(const std::string& udsPath);
/**
* @brief Virtual destructor needed for "delete this;"
*/
@@ -98,6 +107,7 @@ public:
SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend)
SDV_INTERFACE_ENTRY(sdv::ipc::IConnect)
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
//SDV_INTERFACE_CHAIN_BASE(sdv::CSharedLifetimeControlImpl<CWinsockConnection>)
END_SDV_INTERFACE_MAP()
/**
@@ -188,23 +198,28 @@ public:
*/
void DestroyObject() override;
/** @brief Register callback used to remove this connection from manager watchdog storage. */
void SetWatchDogRemoveCallback(std::function<void(const void*)> callback);
private:
std::mutex m_MtxConnect;
std::condition_variable m_CvConnect;
std::thread m_ReceiveThread; ///< Thread which receives data from the socket
std::thread m_ConnectThread;
sdv::core::secure_thread m_ReceiveThread; ///< Thread which receives data from the socket
sdv::core::secure_thread m_ConnectThread;
std::atomic<bool> m_StopReceiveThread{false}; ///< bool variable to stop thread
std::atomic<bool> m_StopConnectThread{false};
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
sdv::ipc::IDataReceiveCallback* m_pReceiver{nullptr}; ///< Receiver to pass the messages if available
sdv::ipc::IConnectEventCallback* m_pMainEvent{nullptr}; ///< Event receiver
sdv::ipc::IConnectEventCallback* m_pRegisteredEvent{nullptr};
bool m_AcceptConnectionRequired; ///< if true connection has to be accepted before receive thread can be started
mutable std::recursive_mutex m_SendMutex; ///< Synchronize all packages to be send
SOCKET m_ListenSocket{INVALID_SOCKET}; ///< Server-side listening socket
SOCKET m_ConnectionSocket{INVALID_SOCKET}; ///< Active connected socket (client <-> server)
std::string m_UdsPath; ///< Client-side AF_UNIX path (lazy connect)
static constexpr uint32_t m_SendMessageSize{ 1024 }; ///< size for the message to be send
static constexpr uint32_t m_SendBufferSize = sizeof(SMsgHeader) + m_SendMessageSize; ///< Initial size of the send buffer
@@ -213,6 +228,9 @@ private:
uint32_t m_ReceiveBufferLength = sizeof(SMsgHeader); ///< receive buffer length
std::atomic<bool> m_CancelWait{false};
std::mutex m_WatchdogMtx;
std::function<void(const void*)> m_WatchdogRemoveCallback;
std::atomic<bool> m_DestroyObjectCalled{false};
/// @brief Server accept loop / client connect confirmation
void ConnectWorker();

View File

@@ -0,0 +1,58 @@
/********************************************************************************
* 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 "watchdog.h"
void CWinSocketsConnectionWatchDog::AddConnectionImpl(const std::shared_ptr<void>& connection)
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_Connections[connection.get()] = connection;
}
void CWinSocketsConnectionWatchDog::RemoveConnection(const void* connection)
{
if (!connection)
{
return;
}
std::shared_ptr<void> removed;
{
std::lock_guard<std::mutex> lock(m_Mutex);
auto it = m_Connections.find(connection);
if (it == m_Connections.end())
{
return;
}
removed = std::move(it->second);
m_Connections.erase(it);
}
removed.reset();
}
void CWinSocketsConnectionWatchDog::Clear()
{
std::map<const void*, std::shared_ptr<void>> localConnections;
{
std::lock_guard<std::mutex> lock(m_Mutex);
localConnections.swap(m_Connections);
}
localConnections.clear();
}
#endif // _WIN32

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
*
* Contributors:
* Denisa Ros - initial API and implementation
********************************************************************************/
#ifdef _WIN32
#ifndef UDS_WIN_SOCKETS_WATCHDOG_H
#define UDS_WIN_SOCKETS_WATCHDOG_H
#include <map>
#include <memory>
#include <mutex>
class CWinSocketsConnectionWatchDog
{
public:
template<typename T>
void AddConnection(const std::shared_ptr<T>& connection)
{
if (!connection)
{
return;
}
AddConnectionImpl(std::static_pointer_cast<void>(connection));
}
void RemoveConnection(const void* connection);
void Clear();
private:
void AddConnectionImpl(const std::shared_ptr<void>& connection);
std::mutex m_Mutex;
std::map<const void*, std::shared_ptr<void>> m_Connections;
};
#endif // UDS_WIN_SOCKETS_WATCHDOG_H
#endif // _WIN32

View File

@@ -8,33 +8,64 @@
# SPDX-License-Identifier: Apache-2.0
#*******************************************************************************
if(WIN32)
# Define project
project(uds_win_tunnel VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(uds_win_tunnel STATIC
set(UDS_WIN_TUNNEL_SOURCES
channel_mgnt.cpp
connection.cpp
watchdog.cpp
)
# Define targets
add_library(uds_win_tunnel STATIC
${UDS_WIN_TUNNEL_SOURCES}
)
add_library(uds_win_tunnel_module SHARED
${UDS_WIN_TUNNEL_SOURCES}
)
target_link_libraries(uds_win_tunnel
PRIVATE
uds_win_sockets
Ws2_32.lib
Ws2_32.lib
${CMAKE_THREAD_LIBS_INIT}
)
target_include_directories(uds_win_tunnel
PRIVATE
target_link_libraries(uds_win_tunnel_module
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")
target_include_directories(uds_win_tunnel_module
PRIVATE
./include/
../uds_win_sockets/
)
set_target_properties(uds_win_tunnel_module PROPERTIES
PREFIX ""
SUFFIX ".sdv"
OUTPUT_NAME "uds_win_tunnel"
ARCHIVE_OUTPUT_NAME "uds_win_tunnel_module"
)
# Build dependencies
add_dependencies(uds_win_tunnel CompileCoreIDL)
add_dependencies(uds_win_tunnel_module CompileCoreIDL)
# Appending the service in the service list
set(SDV_Service_List ${SDV_Service_List} uds_win_tunnel PARENT_SCOPE)
set(SDV_Service_List ${SDV_Service_List} uds_win_tunnel_module PARENT_SCOPE)
endif()

View File

@@ -13,12 +13,14 @@
#ifdef _WIN32
#include "channel_mgnt.h"
#include <chrono>
#include <future>
#include <mutex>
#include <thread>
#include "../../global/base64.h"
#include <support/toml.h>
#include <interfaces/process.h>
#include <future>
#pragma push_macro("interface")
#undef interface
@@ -39,298 +41,498 @@
#pragma pop_macro("GetObject")
#pragma pop_macro("interface")
extern int StartUpWinSock();
//#include "../sdv_services/uds_win_sockets/channel_mgnt.cpp"
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)
static std::atomic<uint32_t> g_nextChannelId{1};
static bool EnsureWSAInitialized()
{
return false;
static std::once_flag s_once;
static bool s_ok = false;
std::call_once(s_once, []()
{
WSADATA wsa{};
const int rc = WSAStartup(MAKEWORD(2, 2), &wsa);
s_ok = (rc == 0);
if (!s_ok)
{
SDV_LOG_ERROR("[AF_UNIX] WSAStartup failed, rc=", rc);
}
});
return s_ok;
}
const auto p = cs.find(pathKey);
if (p == std::string::npos)
static std::string SanitizeUdsName(std::string name)
{
outPath.clear();
for (char& ch : name)
{
const bool isAlphaNum = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9');
if (!isAlphaNum && ch != '_' && ch != '-')
{
ch = '_';
}
}
if (name.empty())
{
name = "sdv";
}
return name;
}
static std::string BuildNamedUdsRawPath(const std::string& channelName)
{
return "%LOCALAPPDATA%/sdv/" + SanitizeUdsName(channelName) + ".sock";
}
static std::string GetDefaultUdsRawPath()
{
uint32_t instanceId = 1000u;
const sdv::app::IAppContext* pAppContext = sdv::core::GetCore<sdv::app::IAppContext>();
if (pAppContext && pAppContext->GetInstanceID() != 0u)
{
instanceId = pAppContext->GetInstanceID();
}
return "%LOCALAPPDATA%/sdv/vapi_" + std::to_string(instanceId) + ".sock";
}
static std::string GetUniqueEndpointUdsRawPath()
{
static std::atomic<uint32_t> nextEndpointId { 0u };
uint32_t instanceId = 1000u;
const sdv::app::IAppContext* pAppContext = sdv::core::GetCore<sdv::app::IAppContext>();
if (pAppContext && pAppContext->GetInstanceID() != 0u)
{
instanceId = pAppContext->GetInstanceID();
}
const uint32_t endpointId = nextEndpointId.fetch_add(1u, std::memory_order_relaxed);
return "%LOCALAPPDATA%/sdv/vapi_" + std::to_string(instanceId) + "_" +
std::to_string(static_cast<uint32_t>(GetCurrentProcessId())) + "_" + std::to_string(endpointId) + ".sock";
}
/**
* @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=";
// Strict raw connect string only
if (cs.rfind(protoKey, 0) != 0)
{
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;
}
const auto start = p + std::strlen(pathKey);
const auto end = cs.find(';', start);
if (end == std::string::npos)
static std::string ExtractTunnelNameFromToml(sdv::toml::CTOMLParser& parser)
{
outPath = cs.substr(start);
}
else
{
outPath = cs.substr(start, end - start);
}
return true;
}
std::string tunnel;
/**
* @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)
auto ipcTunnelNode = parser.GetDirect("IpcChannel.Tunnel");
if (ipcTunnelNode.GetType() == sdv::toml::ENodeType::node_string)
{
tunnel = static_cast<std::string>(ipcTunnelNode.GetValue());
}
if (tunnel.empty())
{
auto providerTunnelNode = parser.GetDirect("Provider.Tunnel");
if (providerTunnelNode.GetType() == sdv::toml::ENodeType::node_string)
{
tunnel = static_cast<std::string>(providerTunnelNode.GetValue());
}
}
return tunnel;
}
static bool ExtractTunnelConnectString(const std::string& in,
std::string& outTunnelConnectString)
{
std::string path;
// Case 1: strict raw tunnel connect string
if (ParseTunnelPath(in, path))
{
outTunnelConnectString = in;
return true;
}
// Case 2: structured TOML
sdv::toml::CTOMLParser parser(in);
if (!parser.IsValid())
{
return false;
}
const std::string providerName = parser.GetDirect("Provider.Name").GetValue();
if (!providerName.empty() &&
providerName != "unix_domain_sockets_tunnel" &&
providerName != "WinTunnelChannelControl" &&
providerName != "UnixTunnelChannelControl")
{
return false;
}
// Provider.ConnectString = "proto=tunnel;path=...;tunnel=...;"
const std::string nested = parser.GetDirect("Provider.ConnectString").GetValue();
if (!nested.empty())
{
if (ParseTunnelPath(nested, path))
{
outTunnelConnectString = nested;
return true;
}
return false;
}
// Build a tunnel connect string from [IpcChannel]
const std::string tunnel = ExtractTunnelNameFromToml(parser);
const std::string cfgPath = parser.GetDirect("IpcChannel.Path").GetValue();
if (!cfgPath.empty())
{
outTunnelConnectString = "proto=tunnel;path=" + cfgPath + ";";
if (!tunnel.empty())
{
outTunnelConnectString += "tunnel=" + tunnel + ";";
}
return true;
}
const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue();
if (!cfgName.empty())
{
outTunnelConnectString = "proto=tunnel;path=" + BuildNamedUdsRawPath(cfgName) + ";";
if (!tunnel.empty())
{
outTunnelConnectString += "tunnel=" + tunnel + ";";
}
return true;
}
return false;
}
/**
* @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;
}
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)
/**
* @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)
{
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";
SOCKADDR_UN tmp{};
constexpr auto kMax = sizeof(tmp.sun_path) - 1;
if (p.size() <= kMax)
{
return p;
}
return p.substr(0, kMax);
}
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)
static std::string BuildFinalUdsPath(const std::string& rawPath)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] socket() FAIL (listen), WSA=", WSAGetLastError());
return INVALID_SOCKET;
// Expand environment variables
std::string full = ExpandEnvVars(rawPath);
// Ensure directory exists (parent folder)
auto pos = full.find_last_of("\\/");
if (pos != std::string::npos)
{
std::string dir = full.substr(0, pos);
// Ensure immediate parent exists (base/tunnel directory is created earlier)
CreateDirectoryA(dir.c_str(), nullptr);
}
// Clamp to AF_UNIX limit
return ClampUdsPath(full);
}
/**
* @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);
}
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)
/**
* @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)
{
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;
}
if (!EnsureWSAInitialized())
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);
SDV_LOG_ERROR("[AF_UNIX] socket FAIL (listen), WSA=", WSAGetLastError());
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;
}
//bulletproof path handling
std::string udsPath = BuildFinalUdsPath(rawPath);
lastError = WSAGetLastError();
closesocket(s);
SOCKADDR_UN addr{};
addr.sun_family = AF_UNIX;
strcpy_s(addr.sun_path, sizeof(addr.sun_path), udsPath.c_str());
if (std::chrono::steady_clock::now() >= deadline)
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] connect TIMEOUT, last WSA=",
lastError, ", path='", udsPath, "'");
int err = WSAGetLastError();
SDV_LOG_ERROR("[AF_UNIX] bind FAIL, WSA=", err, ", path=", udsPath);
closesocket(s);
return INVALID_SOCKET;
}
std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
if (listen(s, SOMAXCONN) == SOCKET_ERROR)
{
int err = WSAGetLastError();
SDV_LOG_ERROR("[AF_UNIX] listen FAIL, WSA=", err, ", path=", udsPath);
closesocket(s);
return INVALID_SOCKET;
}
SDV_LOG_INFO("[AF_UNIX] bind+listen OK: ", udsPath);
return s;
}
}
static bool ExtractTunnelName(const std::string& in, std::string& outTunnel)
{
auto pos = in.find("tunnel=");
if (pos == std::string::npos)
{
outTunnel.clear();
return false;
}
pos += 7;
auto end = in.find(';', pos);
outTunnel = (end == std::string::npos) ? in.substr(pos) : in.substr(pos, end - pos);
return !outTunnel.empty();
}
/**
* @brief Connects to an AF_UNIX socket at the specified path, retrying until timeout.
* @param[in] rawPath The raw path to connect to.CreateUnixListenSocket
* @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)
{
if (!EnsureWSAInitialized())
return INVALID_SOCKET;
//SAME path logic ca server
const std::string udsPath = BuildFinalUdsPath(rawPath);
SDV_LOG_INFO("[AF_UNIX][Tunnel] Attempting to connect to ", udsPath, "with rawPath=", 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);
auto deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(totalTimeoutMs);
while (true)
{
SOCKET s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
SDV_LOG_ERROR("[AF_UNIX] socket FAIL (client), WSA=", WSAGetLastError());
return INVALID_SOCKET;
}
if (connect(s, reinterpret_cast<const sockaddr*>(&addr), addrlen) == 0)
{
SDV_LOG_INFO("[AF_UNIX] connect OK: ", udsPath);
return s;
}
int err = WSAGetLastError();
closesocket(s);
if (std::chrono::steady_clock::now() >= deadline)
{
SDV_LOG_ERROR("[AF_UNIX] connect TIMEOUT, WSA=", err, ", path=", udsPath);
return INVALID_SOCKET;
}
std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
}
}
} // anonymous namespace
bool CSocketsTunnelChannelMgnt::OnInitialize()
{
return true;
return EnsureWSAInitialized();
}
void CSocketsTunnelChannelMgnt::OnShutdown()
{}
// -------- Server bookkeeping (optional) --------
void CSocketsTunnelChannelMgnt::OnServerClosed(const std::string& udsPath, CWinTunnelConnection* ptr)
void CSocketsTunnelChannelMgnt::OnDestroy()
{
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);
m_watchdog.Clear();
}
// -------- ICreateEndpoint --------
// Note:
// tunnel name is required by the current Windows tunnel design for namespace isolation.
// [IpcChannel.Path]/[IpcChannel.Name] alone are not sufficient unless Tunnel is also provided.
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());
}
}
std::string tunnelConnectString;
//for connect string
if (udsRaw.empty())
if (ExtractTunnelConnectString(cfgStr, tunnelConnectString))
{
ParseTunnelPath(tunnelConnectString, udsRaw);
}
else if (!cfgStr.empty())
{
sdv::toml::CTOMLParser parser(cfgStr);
const std::string cfgPath = parser.GetDirect("IpcChannel.Path").GetValue();
const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue();
if (!cfgPath.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());
}
udsRaw = cfgPath;
}
else if (!cfgName.empty())
{
udsRaw = BuildNamedUdsRawPath(cfgName);
}
else
{
udsRaw = GetDefaultUdsRawPath();
}
}
if (udsRaw.empty())
{
udsRaw = "%LOCALAPPDATA%/sdv/tunnel.sock";
udsRaw = cfgStr.empty() ? GetUniqueEndpointUdsRawPath() : GetDefaultUdsRawPath();
}
if (tunnelConnectString.empty())
{
tunnelConnectString = cfgStr;
}
std::string tunnel;
if (!ExtractTunnelName(tunnelConnectString, tunnel))
{
// fallback for AppConnect (config minimal)
sdv::toml::CTOMLParser parser(cfgStr);
const std::string cfgName = parser.GetDirect("IpcChannel.Name").GetValue();
if (!cfgName.empty())
{
tunnel = cfgName;
SDV_LOG_INFO("[AF_UNIX][Tunnel] Using channel name as tunnel name: ", tunnel);
}
else
{
// fallback
tunnel = "default";
SDV_LOG_WARNING("[AF_UNIX][Tunnel] Missing tunnel and channel name, using default");
}
}
std::string udsPathBase = NormalizeUdsPathForWindows(udsRaw);
std::string base = ExpandEnvVars("%TEMP%\\sdv\\");
CreateDirectoryA(base.c_str(), nullptr);
std::string dir = base + tunnel + "\\";
CreateDirectoryA(dir.c_str(), nullptr);
std::string udsPathBase = dir + NormalizeUdsPathForWindows(udsRaw);
SDV_LOG_INFO("[AF_UNIX][Tunnel] endpoint udsPath=", udsPathBase);
SOCKET listenSocket = CreateUnixListenSocket(udsPathBase);
@@ -340,75 +542,177 @@ sdv::ipc::SChannelEndpoint CSocketsTunnelChannelMgnt::CreateEndpoint(const sdv::
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));
auto serverTransport = std::make_shared<CWinsockConnection>(static_cast<unsigned long long>(listenSocket), true);
uint32_t chId = g_nextChannelId++;
auto serverTunnel = std::make_shared<CWinTunnelConnection>(serverTransport, /*channelId*/ static_cast<uint16_t>(chId));
// Ignore cppcheck warning; if construction failed, an exception is expected first.
// cppcheck-suppress knownConditionTrueFalse
if (!serverTunnel)
{
std::lock_guard<std::mutex> lock(m_udsMtx);
m_udsServers[udsPathBase] = serverTunnel;
m_udsServerClaimed.erase(udsPathBase);
return ep;
}
// Retain shared_ptr to keep object alive for the duration of the connection.
// This ensures shared_from_this() works correctly in DestroyObject().
{
std::lock_guard<std::mutex> lock(m_ConnectionsMutex);
m_ptrConnections[serverTunnel.get()] = serverTunnel;
}
serverTunnel->SetWatchDogRemoveCallback([this](const void* connection)
{
m_watchdog.RemoveConnection(connection);
// Remove the retained shared_ptr when connection is destroyed
std::lock_guard<std::mutex> lock(m_ConnectionsMutex);
m_ptrConnections.erase(connection);
});
m_watchdog.AddConnection(serverTunnel);
ep.pConnection = static_cast<sdv::IInterfaceAccess*>(serverTunnel.get());
ep.ssConnectString = "proto=tunnel;role=server;path=" + udsPathBase + ";";
const std::string clientConnectString = "proto=tunnel;path=" + udsPathBase + ";tunnel=" + tunnel + ";";
// Publish raw connect string (cleaner and avoids TOML escaping issues on Windows)
ep.ssConnectString = clientConnectString;
return ep;
}
sdv::IInterfaceAccess* CSocketsTunnelChannelMgnt::Access(const sdv::u8string& cs)
{
if (StartUpWinSock() != 0)
std::string tunnelConnectString;
const std::string input = static_cast<std::string>(cs);
// Operational metadata, not part of the tunnel connect string itself
const bool isServer = (input.find("role=server") != std::string::npos);
// Parse only the real connect/config part
std::string parseInput = input;
const auto rolePos = parseInput.find(";role=");
if (rolePos != std::string::npos)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] WinSock startup failed in Access()" );
const auto roleEnd = parseInput.find(';', rolePos + 1);
if (roleEnd != std::string::npos)
{
parseInput.erase(rolePos, roleEnd - rolePos + 1);
}
else
{
parseInput.erase(rolePos);
}
}
if (!ExtractTunnelConnectString(parseInput, tunnelConnectString))
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] Invalid tunnel connect/config string");
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;
}
ParseTunnelPath(tunnelConnectString, udsRaw);
SDV_LOG_INFO("[AF_UNIX][Tunnel] Access requested with connect string: ",
tunnelConnectString, ", extracted path: ", udsRaw);
if (udsRaw.empty())
{
udsRaw = "%LOCALAPPDATA%/sdv/tunnel.sock";
udsRaw = GetDefaultUdsRawPath();
SDV_LOG_INFO("[AF_UNIX][Tunnel] No path specified, using default: ", udsRaw);
}
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::string tunnel;
if (!ExtractTunnelName(tunnelConnectString, tunnel))
{
std::lock_guard<std::mutex> lock(m_udsMtx);
auto it = m_udsServers.find(udsPathBase);
if (isServer && it != m_udsServers.end() && it->second != nullptr)
// fallback: derive from filename
std::string path;
ParseTunnelPath(tunnelConnectString, path);
// extract filename
auto pos = path.find_last_of("/\\");
std::string filename = (pos != std::string::npos) ? path.substr(pos + 1) : path;
// remove ".sock"
auto dot = filename.rfind(".sock");
if (dot != std::string::npos)
{
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)
}
tunnel = filename.substr(0, dot);
}
else
{
tunnel = filename;
}
if (!tunnel.empty())
{
SDV_LOG_INFO("[AF_UNIX][Tunnel] Derived tunnel from filename: ", tunnel);
}
}
// CLIENT: create AF_UNIX client socket and wrap it in a tunnel
SOCKET s = ConnectUnixSocket(udsPathBase, /*totalTimeoutMs*/ 5000, /*retryDelayMs*/ 50);
if (s == INVALID_SOCKET)
if (tunnel.empty())
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] Failed to connect client socket for ", udsPathBase);
SDV_LOG_ERROR("[AF_UNIX][Tunnel] Missing required tunnel name");
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);
std::string base = ExpandEnvVars("%TEMP%\\sdv\\");
CreateDirectoryA(base.c_str(), nullptr);
return static_cast<sdv::IInterfaceAccess*>(tunnelClient);
std::string dir = base + tunnel + "\\";
CreateDirectoryA(dir.c_str(), nullptr);
std::string udsPathBase = dir + NormalizeUdsPathForWindows(udsRaw);
SDV_LOG_INFO("[AF_UNIX][Tunnel] Access udsPath=", udsPathBase);
std::shared_ptr<CWinTunnelConnection> connection;
if (isServer)
{
SOCKET listenSocket = CreateUnixListenSocket(udsPathBase);
if (listenSocket == INVALID_SOCKET)
{
SDV_LOG_ERROR("[AF_UNIX][Tunnel] Failed to create server socket for ", udsPathBase);
return nullptr;
}
auto serverTransport = std::make_shared<CWinsockConnection>(
static_cast<unsigned long long>(listenSocket), true);
uint32_t chId = g_nextChannelId++;
connection = std::make_shared<CWinTunnelConnection>(
serverTransport, static_cast<uint16_t>(chId));
}
else
{
SOCKET s = ConnectUnixSocket(udsPathBase, 5000, 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, false);
uint32_t chId = g_nextChannelId++;
connection = std::make_shared<CWinTunnelConnection>(
clientTransport, static_cast<uint16_t>(chId));
}
if (!connection)
{
return nullptr;
}
{
std::lock_guard<std::mutex> lock(m_ConnectionsMutex);
m_ptrConnections[connection.get()] = connection;
}
connection->SetWatchDogRemoveCallback([this](const void* instance)
{
m_watchdog.RemoveConnection(instance);
std::lock_guard<std::mutex> lock(m_ConnectionsMutex);
m_ptrConnections.erase(instance);
});
m_watchdog.AddConnection(connection);
return static_cast<sdv::IInterfaceAccess*>(connection.get());
}
#endif

View File

@@ -16,18 +16,15 @@
#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 "watchdog.h"
#include <mutex>
#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <mutex>
#include <string>
// Winsock headers are required for SOCKET / AF_UNIX / WSAStartup
// NOTE: The actual initialization is done via StartUpWinSock()
#include <ws2tcpip.h>
class CWinTunnelConnection;
@@ -56,8 +53,8 @@ public:
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_CLASS_ALIAS("unix_domain_sockets_tunnel")
DECLARE_DEFAULT_OBJECT_NAME("unix_domain_sockets_tunnel")
DECLARE_OBJECT_SINGLETON()
virtual ~CSocketsTunnelChannelMgnt() = default;
@@ -73,6 +70,11 @@ public:
*/
virtual void OnShutdown() override;
/**
* @brief Last function called before destruction. Overload of sdv::CSdvObject::OnDestroy.
*/
virtual void OnDestroy() override;
/**
* @brief Creates a tunnel endpoint (server side) and returns endpoint info.
* @param[in] cfgStr Optional config string (TOML or connect string).
@@ -87,17 +89,10 @@ public:
*/
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;
CWinTunnelConnectionWatchDog m_watchdog;
std::map<const void*, std::shared_ptr<CWinTunnelConnection>> m_ptrConnections; ///< Retain shared_ptr for all connections
mutable std::mutex m_ConnectionsMutex; ///< Protect concurrent access to m_ptrConnections
};
DEFINE_SDV_OBJECT(CSocketsTunnelChannelMgnt)

View File

@@ -24,26 +24,35 @@ CWinTunnelConnection::CWinTunnelConnection(
// No additional initialization required; acts as a thin wrapper.
}
bool CWinTunnelConnection::SendData(
/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
CWinTunnelConnection::~CWinTunnelConnection()
{
}
void CWinTunnelConnection::SetWatchDogRemoveCallback(std::function<void(const void*)> callback)
{
std::lock_guard<std::mutex> lock(m_WatchdogMtx);
m_WatchdogRemoveCallback = std::move(callback);
}
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
hdr.uiChannelId = m_ChannelId;
hdr.uiFlags = 0;
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)
@@ -51,11 +60,13 @@ bool CWinTunnelConnection::SendData(
seqWithHdr.push_back(chunk);
}
bool result = m_Transport->SendData(seqWithHdr);
if (!result) {
const 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)
@@ -70,7 +81,7 @@ bool CWinTunnelConnection::AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver)
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
sdv::TInterfaceAccessPtr acc(pReceiver);
m_pUpperReceiver = acc.GetInterface<sdv::ipc::IDataReceiveCallback>();
m_pUpperRecv = acc.GetInterface<sdv::ipc::IDataReceiveCallback>();
m_pUpperEvent = acc.GetInterface<sdv::ipc::IConnectEventCallback>();
}
@@ -110,14 +121,8 @@ void CWinTunnelConnection::Disconnect()
return;
}
// Keep upper callbacks on plain Disconnect(); they are released in DestroyObject().
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(
@@ -156,55 +161,91 @@ sdv::ipc::EConnectState CWinTunnelConnection::GetConnectState() const
void CWinTunnelConnection::DestroyObject()
{
bool expected = false;
if (!m_DestroyObjectCalled.compare_exchange_strong(expected, true))
{
return;
}
// Stop forwarding to upper layer before transport teardown.
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
m_pUpperRecv = nullptr;
m_pUpperEvent = nullptr;
}
// Do not notify upper layer during destruction teardown to avoid races.
SetConnectState(sdv::ipc::EConnectState::terminating);
Disconnect();
std::lock_guard<std::mutex> lock(m_CallbackMtx);
m_Transport.reset();
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
m_Transport.reset();
}
std::function<void(const void*)> removeCallback;
{
std::lock_guard<std::mutex> watchdogLock(m_WatchdogMtx);
removeCallback = std::move(m_WatchdogRemoveCallback);
}
if (removeCallback)
{
removeCallback(this);
}
}
void CWinTunnelConnection::ReceiveData(
/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
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
return;
}
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));
// Future demux point:
// if (hdr.uiChannelId != m_ChannelId) { ... }
// 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)
sdv::ipc::IDataReceiveCallback* upper = nullptr;
{
try {
m_pUpperReceiver->ReceiveData(payload); // header stripped
} catch (...) {
SDV_LOG_ERROR("[WinTunnel] Exception in upper receiver's ReceiveData");
std::lock_guard<std::mutex> lock(m_CallbackMtx);
upper = m_pUpperRecv;
}
if (upper)
{
try
{
upper->ReceiveData(payload);
}
catch (...)
{
SDV_LOG_ERROR("[WinTunnel] Exception in upper receiver callback");
}
}
}
void CWinTunnelConnection::SetConnectState(sdv::ipc::EConnectState state)
{
sdv::ipc::IConnectEventCallback* upper = nullptr;
{
std::lock_guard<std::mutex> lock(m_CallbackMtx);
upper = m_pUpperEvent;
@@ -218,8 +259,7 @@ void CWinTunnelConnection::SetConnectState(sdv::ipc::EConnectState state)
}
catch (...)
{
SDV_LOG_ERROR("[WinTunnel] Exception in upper event callback's SetConnectState");
// Never let user callback crash the transport.
SDV_LOG_ERROR("[WinTunnel] Exception in upper event callback");
}
}
}

View File

@@ -21,10 +21,12 @@
#include <WinSock2.h>
#include <atomic>
#include <cstdint>
#include <functional>
#include <mutex>
#include <memory>
#include <thread>
#include <vector>
#include <cstring>
#include "../sdv_services/uds_win_sockets/connection.h" // existing AF_UNIX transport: CWinsockConnection
@@ -69,7 +71,7 @@ public:
/**
* @brief Destructor.
*/
virtual ~CWinTunnelConnection() = default;
virtual ~CWinTunnelConnection();
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend)
@@ -153,15 +155,19 @@ public:
// Helpers
void SetChannelId(uint16_t channelId) { m_ChannelId = channelId; }
uint16_t GetChannelId() const noexcept { return m_ChannelId; }
void SetWatchDogRemoveCallback(std::function<void(const void*)> callback);
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::IDataReceiveCallback* m_pUpperRecv{nullptr};
sdv::ipc::IConnectEventCallback* m_pUpperEvent{nullptr};
mutable std::mutex m_CallbackMtx;
std::mutex m_WatchdogMtx;
std::function<void(const void*)> m_WatchdogRemoveCallback;
std::atomic<bool> m_DestroyObjectCalled{false};
};
#endif // UDS_WIN_TUNNEL_CONNECTION_H

View File

@@ -0,0 +1,58 @@
/********************************************************************************
* 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 "watchdog.h"
void CWinTunnelConnectionWatchDog::AddConnectionImpl(const std::shared_ptr<void>& connection)
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_Connections[connection.get()] = connection;
}
void CWinTunnelConnectionWatchDog::RemoveConnection(const void* connection)
{
if (!connection)
{
return;
}
std::shared_ptr<void> removed;
{
std::lock_guard<std::mutex> lock(m_Mutex);
auto it = m_Connections.find(connection);
if (it == m_Connections.end())
{
return;
}
removed = std::move(it->second);
m_Connections.erase(it);
}
removed.reset();
}
void CWinTunnelConnectionWatchDog::Clear()
{
std::map<const void*, std::shared_ptr<void>> localConnections;
{
std::lock_guard<std::mutex> lock(m_Mutex);
localConnections.swap(m_Connections);
}
localConnections.clear();
}
#endif // _WIN32

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
*
* Contributors:
* Denisa Ros - initial API and implementation
********************************************************************************/
#ifdef _WIN32
#ifndef UDS_WIN_TUNNEL_WATCHDOG_H
#define UDS_WIN_TUNNEL_WATCHDOG_H
#include <map>
#include <memory>
#include <mutex>
class CWinTunnelConnectionWatchDog
{
public:
template<typename T>
void AddConnection(const std::shared_ptr<T>& connection)
{
if (!connection)
{
return;
}
AddConnectionImpl(std::static_pointer_cast<void>(connection));
}
void RemoveConnection(const void* connection);
void Clear();
private:
void AddConnectionImpl(const std::shared_ptr<void>& connection);
std::mutex m_Mutex;
std::map<const void*, std::shared_ptr<void>> m_Connections;
};
#endif // UDS_WIN_TUNNEL_WATCHDOG_H
#endif // _WIN32