mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-02-05 15:18:45 +00:00
199
sdv_services/CMakeLists.txt
Normal file
199
sdv_services/CMakeLists.txt
Normal file
@@ -0,0 +1,199 @@
|
||||
# Include cross-compilation toolchain file
|
||||
include(../cross-compile-tools.cmake)
|
||||
|
||||
# Use new policy for project version settings and default warning level
|
||||
cmake_policy(SET CMP0048 NEW) # requires CMake 3.14
|
||||
cmake_policy(SET CMP0092 NEW) # requires CMake 3.15
|
||||
|
||||
# Define project
|
||||
project(SDVServices VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Include export into the include directory path
|
||||
include_directories(../export)
|
||||
|
||||
# Compile the IDL
|
||||
set(INTERFACE_DIR ${PROJECT_SOURCE_DIR}/../export/interfaces)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/app.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/app.idl
|
||||
COMMENT "Compiling app.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/app.idl -O${INTERFACE_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/config.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/config.idl
|
||||
COMMENT "Compiling config.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/config.idl -O${INTERFACE_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/can.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/can.idl
|
||||
COMMENT "Compiling can.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/can.idl -O${INTERFACE_DIR} --no_ps
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/toml.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/toml.idl
|
||||
COMMENT "Compiling toml.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/toml.idl -O${INTERFACE_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/core.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/core.idl
|
||||
COMMENT "Compiling core.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/core.idl -O${INTERFACE_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/core_idl.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/core_idl.idl
|
||||
COMMENT "Compiling core_idl.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/core_idl.idl -O${INTERFACE_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/core_ps.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/core_ps.idl
|
||||
COMMENT "Compiling core_ps.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/core_ps.idl -O${INTERFACE_DIR} --no_ps
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/core_types.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/core_types.idl
|
||||
COMMENT "Compiling core_types.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/core_types.idl -O${INTERFACE_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/dispatch.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/dispatch.idl
|
||||
COMMENT "Compiling dispatch.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/dispatch.idl -O${INTERFACE_DIR} --no_ps
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/hw_ident.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/hw_ident.idl
|
||||
COMMENT "Compiling hw_ident.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/hw_ident.idl -O${INTERFACE_DIR} --no_ps
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/ipc.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/ipc.idl
|
||||
COMMENT "Compiling ipc.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/ipc.idl -O${INTERFACE_DIR} --no_ps
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/com.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/com.idl
|
||||
COMMENT "Compiling com.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/com.idl -O${INTERFACE_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/log.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/log.idl
|
||||
COMMENT "Compiling log.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/log.idl -O${INTERFACE_DIR} --no_ps
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/mem.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/mem.idl
|
||||
COMMENT "Compiling mem.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/mem.idl -O${INTERFACE_DIR} --no_ps
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/module.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/module.idl
|
||||
COMMENT "Compiling module.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/module.idl -O${INTERFACE_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/process.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/process.idl
|
||||
COMMENT "Compiling process.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/process.idl -O${INTERFACE_DIR} --no_ps
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/repository.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/repository.idl
|
||||
COMMENT "Compiling repository.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/repository.idl -O${INTERFACE_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT ${INTERFACE_DIR}/timer.h
|
||||
DEPENDS sdv_idl_compiler
|
||||
MAIN_DEPENDENCY ${INTERFACE_DIR}/timer.idl
|
||||
COMMENT "Compiling timer.idl"
|
||||
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/timer.idl -O${INTERFACE_DIR}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(CompileCoreIDL
|
||||
DEPENDS
|
||||
${INTERFACE_DIR}/app.h
|
||||
${INTERFACE_DIR}/config.h
|
||||
${INTERFACE_DIR}/can.h
|
||||
${INTERFACE_DIR}/toml.h
|
||||
${INTERFACE_DIR}/core.h
|
||||
${INTERFACE_DIR}/core_idl.h
|
||||
${INTERFACE_DIR}/core_ps.h
|
||||
${INTERFACE_DIR}/core_types.h
|
||||
${INTERFACE_DIR}/dispatch.h
|
||||
${INTERFACE_DIR}/hw_ident.h
|
||||
${INTERFACE_DIR}/ipc.h
|
||||
${INTERFACE_DIR}/com.h
|
||||
${INTERFACE_DIR}/log.h
|
||||
${INTERFACE_DIR}/mem.h
|
||||
${INTERFACE_DIR}/module.h
|
||||
${INTERFACE_DIR}/process.h
|
||||
${INTERFACE_DIR}/repository.h
|
||||
${INTERFACE_DIR}/timer.h
|
||||
)
|
||||
|
||||
# Add service projects
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(data_dispatch_service)
|
||||
add_subdirectory(proxy_stub)
|
||||
add_subdirectory(can_communication_socket_can)
|
||||
add_subdirectory(can_communication_silkit)
|
||||
add_subdirectory(can_communication_sim)
|
||||
add_subdirectory(task_timer)
|
||||
add_subdirectory(ipc_com)
|
||||
add_subdirectory(ipc_connect)
|
||||
add_subdirectory(ipc_shared_mem)
|
||||
add_subdirectory(ipc_sockets)
|
||||
add_subdirectory(process_control)
|
||||
add_subdirectory(hardware_ident)
|
||||
add_subdirectory(manifest_util)
|
||||
|
||||
# Appending all services to the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} PARENT_SCOPE)
|
||||
65
sdv_services/can_communication_silkit/CMakeLists.txt
Normal file
65
sdv_services/can_communication_silkit/CMakeLists.txt
Normal file
@@ -0,0 +1,65 @@
|
||||
# Only build on MSVC/Windows or 64-bit Linux (not ARM)
|
||||
if((WIN32 AND NOT MSVC) OR (UNIX AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64"))
|
||||
|
||||
# Define project
|
||||
project(can_com_silkit VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Platform-specific SilKit flavor and library settings
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(SILKIT_FLAVOR "ubuntu-18.04-x86_64-gcc" CACHE STRING "SIL Kit package flavor for Linux")
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(SILKIT_FLAVOR "Win-x86_64-VS2017" CACHE STRING "SIL Kit package flavor for Windows")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported platform for SilKit build.")
|
||||
endif()
|
||||
|
||||
set(SILKIT_VERSION "4.0.37" CACHE STRING "SIL Kit package is specified, this version will be downloaded")
|
||||
message ("Build SilKit component on system: ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
|
||||
# Fetch SIL Kit from github.com
|
||||
message(STATUS "Attempting to fetch [SilKit-${SILKIT_VERSION}-${SILKIT_FLAVOR}] from github.com")
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
silkit
|
||||
URL https://github.com/vectorgrp/sil-kit/releases/download/sil-kit%2Fv${SILKIT_VERSION}/SilKit-${SILKIT_VERSION}-${SILKIT_FLAVOR}.zip
|
||||
DOWNLOAD_DIR ${CMAKE_CURRENT_LIST_DIR}/sil-kit
|
||||
)
|
||||
|
||||
message(STATUS "SIL Kit: fetching [SilKit-${SILKIT_VERSION}-${SILKIT_FLAVOR}]")
|
||||
FetchContent_MakeAvailable(silkit)
|
||||
|
||||
message(STATUS "SIL Kit: using source code from: \"${silkit_SOURCE_DIR}\"")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(SILKIT_LIB "${silkit_SOURCE_DIR}/SilKit/lib/libSilKit.so")
|
||||
message(STATUS "Expecting libSilKit.so at: ${SILKIT_LIB}")
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(SILKIT_LIB "${silkit_SOURCE_DIR}/SilKit/lib/SilKit.lib")
|
||||
message(STATUS "Expecting SilKit.lib at: ${SILKIT_LIB}")
|
||||
else()
|
||||
message(FATAL_ERROR "SilKit libraries not found at: ${SILKIT_LIB}. Check the SilKit flavor, version, and extraction path.")
|
||||
endif()
|
||||
|
||||
set(SilKit_DIR "${silkit_SOURCE_DIR}/SilKit/lib/cmake/SilKit")
|
||||
message(STATUS "SilKit_DIR: ${SilKit_DIR}")
|
||||
|
||||
find_package(SilKit REQUIRED CONFIG)
|
||||
|
||||
add_library(can_com_silkit SHARED "can_com_silkit.h" "can_com_silkit.cpp")
|
||||
|
||||
# Only add -Wno-shadow for GCC or Clang
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
target_compile_options(can_com_silkit PRIVATE -Wno-shadow)
|
||||
endif()
|
||||
|
||||
target_link_libraries(can_com_silkit PRIVATE "${SILKIT_LIB}" ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_include_directories(can_com_silkit PRIVATE "${silkit_SOURCE_DIR}/SilKit/include")
|
||||
|
||||
set_target_properties(can_com_silkit PROPERTIES PREFIX "")
|
||||
|
||||
set_target_properties(can_com_silkit PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
add_dependencies(can_com_silkit CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} can_com_silkit PARENT_SCOPE)
|
||||
endif() # SilKit library is not compatible for ARM and MINGW
|
||||
549
sdv_services/can_communication_silkit/can_com_silkit.cpp
Normal file
549
sdv_services/can_communication_silkit/can_com_silkit.cpp
Normal file
@@ -0,0 +1,549 @@
|
||||
#include "can_com_silkit.h"
|
||||
#include <support/toml.h>
|
||||
#include <bitset>
|
||||
|
||||
void CCANSilKit::Initialize(const sdv::u8string& rssObjectConfig)
|
||||
{
|
||||
std::string silKitNetwork = "";
|
||||
std::string silKitJSONConfigContent = "";
|
||||
std::string silKitRegistryUri = "";
|
||||
try
|
||||
{
|
||||
sdv::toml::CTOMLParser config(rssObjectConfig.c_str());
|
||||
sdv::toml::CNode nodeSilKitConfig = config.GetDirect("SilKitConfig");
|
||||
if (nodeSilKitConfig.GetType() == sdv::toml::ENodeType::node_string)
|
||||
{
|
||||
silKitJSONConfigContent = static_cast<std::string>(nodeSilKitConfig.GetValue());
|
||||
}
|
||||
|
||||
sdv::toml::CNode nodeSilKitParticipant = config.GetDirect("SilKitParticipantName");
|
||||
if (nodeSilKitParticipant.GetType() == sdv::toml::ENodeType::node_string)
|
||||
{
|
||||
m_SilKitParticipantName = static_cast<std::string>(nodeSilKitParticipant.GetValue());
|
||||
}
|
||||
|
||||
sdv::toml::CNode nodeSilKitNetwork = config.GetDirect("CanSilKitNetwork");
|
||||
if (nodeSilKitNetwork.GetType() == sdv::toml::ENodeType::node_string)
|
||||
{
|
||||
silKitNetwork = static_cast<std::string>(nodeSilKitNetwork.GetValue());
|
||||
}
|
||||
|
||||
sdv::toml::CNode nodeSilKitRegistryURI = config.GetDirect("RegistryURI");
|
||||
if (nodeSilKitRegistryURI.GetType() == sdv::toml::ENodeType::node_string)
|
||||
{
|
||||
silKitRegistryUri = static_cast<std::string>(nodeSilKitRegistryURI.GetValue());
|
||||
}
|
||||
|
||||
sdv::toml::CNode nodeSilKitSyncMode = config.GetDirect("SyncMode");
|
||||
if (nodeSilKitSyncMode.GetType() == sdv::toml::ENodeType::node_boolean)
|
||||
{
|
||||
m_SilKitIsSynchronousMode = static_cast<bool>(nodeSilKitSyncMode.GetValue());
|
||||
}
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException& e)
|
||||
{
|
||||
SDV_LOG_ERROR("Configuration could not be read: ", e.what());
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
m_TimerSimulationStep = sdv::core::GetObject<sdv::core::ITimerSimulationStep>("SimulationTaskTimerService");
|
||||
|
||||
if (!ValidateConfiguration(silKitJSONConfigContent, silKitNetwork, silKitRegistryUri))
|
||||
{
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CreateSilKitConnection(silKitJSONConfigContent, silKitNetwork, silKitRegistryUri))
|
||||
{
|
||||
SDV_LOG_ERROR("Error createing SilKit connection, probably invalid JSON content");
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update status only if initialization was successful
|
||||
m_eStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
bool CCANSilKit::ValidateConfiguration(const std::string& ssSilKitJSONConfigContent, const std::string& ssSilKitNetwork, const std::string& ssSilKitRegistryUri)
|
||||
{
|
||||
bool success = true;
|
||||
SDV_LOG_INFO("SilKit connecting to network: ", ssSilKitNetwork.c_str());
|
||||
SDV_LOG_INFO("SilKit registry URI: ", ssSilKitRegistryUri.c_str());
|
||||
m_SilKitIsSynchronousMode ? SDV_LOG_INFO("SilKit is running in synchronous mode.") : SDV_LOG_INFO("SilKit is running in asynchronous mode.");
|
||||
m_TimerSimulationStep ? SDV_LOG_WARNING("Run simulation with simulation timer service.") : SDV_LOG_WARNING("Run simulation with real time service.");
|
||||
|
||||
if (ssSilKitJSONConfigContent.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Error reading config file content for SilKit, missing 'SilKitConfig'.");
|
||||
success = false;
|
||||
}
|
||||
if (m_SilKitParticipantName.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("SilKit CAN participant is not found in configuration, missing 'SilKitParticipantName'.");
|
||||
success = false;
|
||||
}
|
||||
if (ssSilKitNetwork.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Error reading SilKit network name, missing 'CanSilKitNetwork'.");
|
||||
success = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SDV_LOG_INFO("SilKit connecting to network: ", ssSilKitNetwork.c_str());
|
||||
}
|
||||
if (ssSilKitRegistryUri.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Error reading SilKit registry URI, missing 'RegistryURI'.");
|
||||
success = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SDV_LOG_INFO("SilKit registry URI: ", ssSilKitRegistryUri.c_str());
|
||||
}
|
||||
|
||||
if (!m_TimerSimulationStep)
|
||||
{
|
||||
SDV_LOG_WARNING("Run simulation with real timer.");
|
||||
}
|
||||
|
||||
if (m_SilKitIsSynchronousMode)
|
||||
{
|
||||
SDV_LOG_INFO("SilKit is running in synchronous mode.");
|
||||
}
|
||||
else
|
||||
{
|
||||
SDV_LOG_INFO("SilKit is running in asynchronous mode.");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CCANSilKit::GetStatus() const
|
||||
{
|
||||
return m_eStatus;
|
||||
}
|
||||
|
||||
void CCANSilKit::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eStatus == sdv::EObjectStatus::running || m_eStatus == sdv::EObjectStatus::initialized)
|
||||
m_eStatus = sdv::EObjectStatus::configuring;
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eStatus == sdv::EObjectStatus::configuring || m_eStatus == sdv::EObjectStatus::initialized)
|
||||
m_eStatus = sdv::EObjectStatus::running;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CCANSilKit::Shutdown()
|
||||
{
|
||||
SDV_LOG_INFO("Initiating shutdown process...");
|
||||
|
||||
m_eStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
|
||||
try
|
||||
{
|
||||
if (m_SilKitLifeCycleService)
|
||||
{
|
||||
SDV_LOG_INFO("Stopping SilKit Lifecycle Service...");
|
||||
m_SilKitLifeCycleService->Stop("Shutdown requested.");
|
||||
m_SilKitLifeCycleService = nullptr;
|
||||
}
|
||||
|
||||
if (m_SilKitCanController)
|
||||
{
|
||||
SDV_LOG_INFO("Stopping SilKit CAN Controller...");
|
||||
m_SilKitCanController->Stop();
|
||||
m_SilKitCanController = nullptr;
|
||||
}
|
||||
|
||||
if (m_SilKitParticipant)
|
||||
{
|
||||
SDV_LOG_INFO("Resetting SilKit Participant...");
|
||||
m_SilKitParticipant.reset();
|
||||
m_SilKitParticipant = nullptr;
|
||||
}
|
||||
}
|
||||
catch (const SilKit::SilKitError& e)
|
||||
{
|
||||
SDV_LOG_ERROR("SilKit exception occurred during shutdown: ", e.what());
|
||||
m_eStatus = sdv::EObjectStatus::runtime_error;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
SDV_LOG_ERROR("Unknown exception occurred during shutdown: ", e.what());
|
||||
m_eStatus = sdv::EObjectStatus::runtime_error;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_QueueMutex);
|
||||
while (!m_MessageQueue.empty())
|
||||
{
|
||||
m_MessageQueue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void CCANSilKit::RegisterReceiver(/*in*/ sdv::can::IReceive* pReceiver)
|
||||
{
|
||||
if (m_eStatus != sdv::EObjectStatus::configuring)
|
||||
return;
|
||||
|
||||
if (!pReceiver)
|
||||
{
|
||||
SDV_LOG_ERROR("No CAN receiver available.");
|
||||
return;
|
||||
}
|
||||
|
||||
SDV_LOG_INFO("Registering VAPI CAN communication receiver...");
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_ReceiversMtx);
|
||||
if (m_SetReceivers.find(pReceiver) == m_SetReceivers.end())
|
||||
{
|
||||
m_SetReceivers.insert(pReceiver);
|
||||
SDV_LOG_INFO("Receiver registered successfully.");
|
||||
}
|
||||
else
|
||||
{
|
||||
SDV_LOG_INFO("Receiver is already registered.");
|
||||
}
|
||||
}
|
||||
|
||||
void CCANSilKit::UnregisterReceiver(/*in*/ sdv::can::IReceive* pReceiver)
|
||||
{
|
||||
// NOTE: Normally the remove function should be called in the configuration mode. Since it doesn't give
|
||||
// feedback and the associated caller might delete any receiving function, allow the removal to take place even
|
||||
// when running.
|
||||
|
||||
if (!pReceiver)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SDV_LOG_INFO("Unregistering VAPI CAN communication receiver...");
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_ReceiversMtx);
|
||||
m_SetReceivers.erase(pReceiver);
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::u8string> CCANSilKit::GetInterfaces() const
|
||||
{
|
||||
sdv::sequence<sdv::u8string> seqIfcNames;
|
||||
if (m_eStatus != sdv::EObjectStatus::running) return seqIfcNames;
|
||||
|
||||
seqIfcNames.push_back(m_SilKitParticipantName);
|
||||
|
||||
return seqIfcNames;
|
||||
}
|
||||
|
||||
std::shared_ptr<SilKit::Config::IParticipantConfiguration> CCANSilKit::GetSilKitConfig(const std::string& ssSilKitJSONConfigContent)
|
||||
{
|
||||
// Get the SilKit configuration from the config file.
|
||||
//auto silKitParticipantCconfig = std::move(SilKit::Config::ParticipantConfigurationFromString(ssSilKitJSONConfigContent));
|
||||
auto silKitParticipantCconfig = SilKit::Config::ParticipantConfigurationFromString(ssSilKitJSONConfigContent);
|
||||
if (silKitParticipantCconfig == nullptr)
|
||||
{
|
||||
SDV_LOG_ERROR("Error parsing the SilKit config content: ", ssSilKitJSONConfigContent.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return silKitParticipantCconfig;
|
||||
}
|
||||
|
||||
std::unique_ptr<SilKit::IParticipant> CCANSilKit::CreateParticipantFromJSONConfig(const std::string& ssSilKitJSONConfigContent, const std::string& ssSilKitRegistryUri)
|
||||
{
|
||||
auto silKitParticipantConfig = GetSilKitConfig(ssSilKitJSONConfigContent);
|
||||
if (silKitParticipantConfig == nullptr)
|
||||
{
|
||||
SDV_LOG_ERROR("The SilKit configuaration file could not be opened.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: get participant name from the configuration
|
||||
// participant name must be unique in the SilKit network setup and must exit before running thee test
|
||||
// 1 single participant: use complete name from the config
|
||||
// more than one channel: do we need more that on participants or can one participant include multiple channels?
|
||||
// in case every single channel requires another participant name: add channel to the name ['config_name' + 'channel']
|
||||
|
||||
SDV_LOG_INFO("Creating SilKit participant: ", m_SilKitParticipantName.c_str());
|
||||
|
||||
return SilKit::CreateParticipant(silKitParticipantConfig, m_SilKitParticipantName, ssSilKitRegistryUri);
|
||||
}
|
||||
|
||||
SilKit::Services::Can::ICanController* CCANSilKit::CreateController(const std::string& ssSilKitNetwork)
|
||||
{
|
||||
/* Create the SilKit CAN controller. */
|
||||
SilKit::Services::Can::ICanController* silKitCanController = m_SilKitParticipant->CreateCanController(m_SilKitParticipantName, ssSilKitNetwork);
|
||||
if (silKitCanController == nullptr)
|
||||
{
|
||||
SDV_LOG_ERROR("SilKit CAN controller is not available.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Register the SilKit transmit status handler (use lambda function to map to member variable). */
|
||||
silKitCanController->AddFrameTransmitHandler(
|
||||
[this](SilKit::Services::Can::ICanController* /*ctrl*/, const SilKit::Services::Can::CanFrameTransmitEvent& rs_transmit_acknowledge)
|
||||
{
|
||||
SilKitTransmitAcknowledgeHandler(rs_transmit_acknowledge);
|
||||
},
|
||||
static_cast<SilKit::Services::Can::CanTransmitStatusMask>(SilKit::Services::Can::CanTransmitStatus::Transmitted) |
|
||||
static_cast<SilKit::Services::Can::CanTransmitStatusMask>(SilKit::Services::Can::CanTransmitStatus::Canceled) |
|
||||
static_cast<SilKit::Services::Can::CanTransmitStatusMask>(SilKit::Services::Can::CanTransmitStatus::TransmitQueueFull));
|
||||
|
||||
/* Register the SilKit receive handler (use lambda function to map to member variable). */
|
||||
silKitCanController->AddFrameHandler(
|
||||
[this](SilKit::Services::Can::ICanController* /*ctrl*/, const SilKit::Services::Can::CanFrameEvent& rs_frame_event)
|
||||
{
|
||||
SilKitReceiveMessageHandler(rs_frame_event.frame);
|
||||
},
|
||||
static_cast<SilKit::Services::DirectionMask>(SilKit::Services::TransmitDirection::RX)
|
||||
/*| static_cast<SilKit::Services::DirectionMask>(SilKit::Services::TransmitDirection::TX)*/);
|
||||
|
||||
return silKitCanController;
|
||||
}
|
||||
|
||||
SilKit::Services::Orchestration::ILifecycleService* CCANSilKit::CreateSilKitLifecycleService()
|
||||
{
|
||||
SilKit::Services::Orchestration::OperationMode silkit_operationMode = (
|
||||
m_SilKitIsSynchronousMode ? SilKit::Services::Orchestration::OperationMode::Coordinated
|
||||
: SilKit::Services::Orchestration::OperationMode::Autonomous
|
||||
);
|
||||
|
||||
return m_SilKitParticipant->CreateLifecycleService({ silkit_operationMode });
|
||||
}
|
||||
|
||||
bool CCANSilKit::SetHandlerFunctions(SilKit::Services::Orchestration::ILifecycleService* silKitLifeCycleService)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Set a SilKit Stop Handler
|
||||
silKitLifeCycleService->SetStopHandler([this]()
|
||||
{
|
||||
m_eStatus = sdv::EObjectStatus::runtime_error;
|
||||
SDV_LOG_INFO("SilKit StopHandlerhandler called");
|
||||
});
|
||||
|
||||
// Set a Shutdown Handler
|
||||
silKitLifeCycleService->SetShutdownHandler([this]()
|
||||
{
|
||||
m_eStatus = sdv::EObjectStatus::runtime_error;
|
||||
SDV_LOG_INFO("SilKit Shutdown handler called");
|
||||
});
|
||||
|
||||
// Set a Shutdown Handler
|
||||
silKitLifeCycleService->SetAbortHandler([this](auto /*participantState*/)
|
||||
{
|
||||
m_eStatus = sdv::EObjectStatus::runtime_error;
|
||||
SDV_LOG_INFO("SilKit Abort handler called");
|
||||
});
|
||||
|
||||
silKitLifeCycleService->SetCommunicationReadyHandler([this]()
|
||||
{
|
||||
m_SilKitCanController->SetBaudRate(10'000, 1'000'000, 2'000'000);
|
||||
m_SilKitCanController->Start();
|
||||
});
|
||||
}
|
||||
|
||||
catch (const SilKit::SilKitError& e)
|
||||
{
|
||||
SDV_LOG_ERROR("SilKit exception occurred when setting the silkit handlers.", e.what());
|
||||
return false;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
SDV_LOG_ERROR("Unknown std exception occurred when setting the silkit handlers.", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCANSilKit::CreateSilKitConnection(const std::string& ssSilKitJSONConfigContent, const std::string& ssSilKitNetwork, const std::string& ssSilKitRegistryUri)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_SilKitParticipant = CreateParticipantFromJSONConfig(ssSilKitJSONConfigContent, ssSilKitRegistryUri);
|
||||
if (m_SilKitParticipant == nullptr)
|
||||
{
|
||||
SDV_LOG_ERROR("SilKit COM adapter is not available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_SilKitCanController = CreateController(ssSilKitNetwork);
|
||||
if (m_SilKitCanController == nullptr)
|
||||
{
|
||||
SDV_LOG_ERROR("SilKit CAN controller is not available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_SilKitLifeCycleService = CreateSilKitLifecycleService();
|
||||
if (!SetHandlerFunctions(m_SilKitLifeCycleService))
|
||||
{
|
||||
SDV_LOG_ERROR("SilKit handler functions could not be set.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_SilKitIsSynchronousMode)
|
||||
{
|
||||
SetupTimeSyncService();
|
||||
}
|
||||
|
||||
auto finalStateFuture = m_SilKitLifeCycleService->StartLifecycle();
|
||||
if (!finalStateFuture.valid())
|
||||
{
|
||||
SDV_LOG_ERROR("Participant State = SilKit::Services::Orchestration::ParticipantState::Invalid");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (const SilKit::SilKitError& e)
|
||||
{
|
||||
SDV_LOG_ERROR("SilKit exception occurred in CreateSilKitConnection().", e.what());
|
||||
return false;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
SDV_LOG_ERROR("Unknown std exception in CreateSilKitConnection():", e.what());
|
||||
return false;
|
||||
}
|
||||
/* Adapter is configured now, return true */
|
||||
return true;
|
||||
}
|
||||
|
||||
void CCANSilKit::SilKitReceiveMessageHandler(const SilKit::Services::Can::CanFrame& rsSilKitCanFrame)
|
||||
{
|
||||
if (m_eStatus != sdv::EObjectStatus::running)
|
||||
return;
|
||||
|
||||
if (rsSilKitCanFrame.dlc > m_maxCanDataLength)
|
||||
{
|
||||
SDV_LOG_WARNING("Invalid data length.");
|
||||
return;
|
||||
}
|
||||
|
||||
sdv::can::SMessage sSDVCanMessage{};
|
||||
sSDVCanMessage.uiID = rsSilKitCanFrame.canId;
|
||||
for (size_t nDataIndex = 0; nDataIndex < static_cast<size_t>(rsSilKitCanFrame.dlc); nDataIndex++)
|
||||
{
|
||||
sSDVCanMessage.seqData.push_back(rsSilKitCanFrame.dataField[nDataIndex]);
|
||||
}
|
||||
|
||||
// Broadcast the message to the receivers
|
||||
std::unique_lock<std::mutex> lockReceivers(m_ReceiversMtx);
|
||||
for (sdv::can::IReceive* pReceiver : m_SetReceivers)
|
||||
{
|
||||
pReceiver->Receive(sSDVCanMessage, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void CCANSilKit::SilKitTransmitAcknowledgeHandler(const SilKit::Services::Can::CanFrameTransmitEvent& rsSilKitTransmitAcknowledge)
|
||||
{
|
||||
/* Get the Acknowledge Sync structure. */
|
||||
SAcknowledgeSync* acknowledgeSync = reinterpret_cast<SAcknowledgeSync*>(rsSilKitTransmitAcknowledge.userContext);
|
||||
if (acknowledgeSync == nullptr)
|
||||
return;
|
||||
|
||||
/* Synchronize threads. */
|
||||
std::unique_lock<std::mutex> lock(acknowledgeSync->mtx);
|
||||
|
||||
/* Copy the content of the transmit status to the sync structure. */
|
||||
*static_cast<SilKit::Services::Can::CanFrameTransmitEvent *>(acknowledgeSync) = rsSilKitTransmitAcknowledge;
|
||||
|
||||
/* Trigger the condition variable. */
|
||||
acknowledgeSync->bProcessed = true;
|
||||
lock.unlock();
|
||||
acknowledgeSync->cv.notify_all();
|
||||
}
|
||||
|
||||
void CCANSilKit::Send(/*in*/ const sdv::can::SMessage& sSDVCanMessage, /*in*/ uint32_t)
|
||||
{
|
||||
if (m_eStatus != sdv::EObjectStatus::running)
|
||||
return;
|
||||
|
||||
if(sSDVCanMessage.bCanFd)
|
||||
{
|
||||
SDV_LOG_ERROR("CAN-FD not supported.");
|
||||
return; // CAN-FD not supported
|
||||
// s_SilKit_message.flags |= static_cast< SilKit::Services::Can::CanFrameFlagMask>( SilKit::Services::Can::CanFrameFlag::Fdf) // FD Format Indicator
|
||||
// | static_cast< SilKit::Services::Can::CanFrameFlagMask>( SilKit::Services::Can::CanFrameFlag::Brs); // Bit Rate Switch (for FD Format only)
|
||||
}
|
||||
|
||||
if (sSDVCanMessage.seqData.size() > m_maxCanDataLength) // Invalid message length.
|
||||
{
|
||||
SDV_LOG_ERROR("Invalid message length:", sSDVCanMessage.seqData.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the message in async mode
|
||||
if (!m_SilKitIsSynchronousMode)
|
||||
{
|
||||
SilKit::Services::Can::CanFrame silKitMessage{};
|
||||
silKitMessage.canId = sSDVCanMessage.uiID;
|
||||
|
||||
std::vector<uint8_t> vecData;
|
||||
for (uint8_t index = 0; index < sSDVCanMessage.seqData.size(); index++)
|
||||
{
|
||||
vecData.emplace_back(sSDVCanMessage.seqData[index]);
|
||||
}
|
||||
silKitMessage.dataField = vecData;
|
||||
m_SilKitCanController->SendFrame(silKitMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_QueueMutex);
|
||||
m_MessageQueue.push(sSDVCanMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void CCANSilKit::SetupTimeSyncService()
|
||||
{
|
||||
auto silKitTimeSyncService = m_SilKitLifeCycleService->CreateTimeSyncService();
|
||||
|
||||
silKitTimeSyncService->SetSimulationStepHandler([this](std::chrono::nanoseconds /*now*/, std::chrono::nanoseconds duration)
|
||||
{
|
||||
if (m_TimerSimulationStep)
|
||||
{
|
||||
m_TimerSimulationStep->SimulationStep(duration.count() / 1000);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_QueueMutex);
|
||||
while (!m_MessageQueue.empty())
|
||||
{
|
||||
const sdv::can::SMessage& frontMsg = m_MessageQueue.front();
|
||||
|
||||
SilKit::Services::Can::CanFrame silKitMessage{};
|
||||
silKitMessage.canId = frontMsg.uiID;
|
||||
// Clamp or check to avoid narrowing conversion warning
|
||||
if (frontMsg.seqData.size() > std::numeric_limits<uint16_t>::max()) {
|
||||
SDV_LOG_WARNING("CAN data length exceeds uint16_t max, truncating.");
|
||||
silKitMessage.dlc = static_cast<uint16_t>(std::numeric_limits<uint16_t>::max());
|
||||
} else {
|
||||
silKitMessage.dlc = static_cast<uint16_t>(frontMsg.seqData.size());
|
||||
}
|
||||
std::vector<uint8_t> vecData;
|
||||
for (uint8_t index = 0; index < frontMsg.seqData.size(); index++)
|
||||
{
|
||||
vecData.emplace_back(frontMsg.seqData[index]);
|
||||
}
|
||||
silKitMessage.dataField = vecData;
|
||||
|
||||
m_SilKitCanController->SendFrame(silKitMessage);
|
||||
m_MessageQueue.pop();
|
||||
}
|
||||
}
|
||||
catch (const SilKit::SilKitError& e)
|
||||
{
|
||||
SDV_LOG_ERROR("SilKit exception occurred when setting up TimeSyncService.", e.what());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
SDV_LOG_ERROR("Unknown std exception when setting up TimeSyncService.", e.what());
|
||||
}
|
||||
}, std::chrono::milliseconds(1));
|
||||
}
|
||||
207
sdv_services/can_communication_silkit/can_com_silkit.h
Normal file
207
sdv_services/can_communication_silkit/can_com_silkit.h
Normal file
@@ -0,0 +1,207 @@
|
||||
#ifndef CAN_COM_SILKIT_H
|
||||
#define CAN_COM_SILKIT_H
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
|
||||
//VAPI includes
|
||||
#include <interfaces/can.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <support/timer.h>
|
||||
|
||||
//SilKit includes
|
||||
#include "silkit/SilKit.hpp"
|
||||
|
||||
/**
|
||||
* @brief Component to establish Socket CAN communication between VAPI and external application
|
||||
*/
|
||||
class CCANSilKit : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::can::IRegisterReceiver,
|
||||
public sdv::can::ISend, sdv::can::IInformation
|
||||
{
|
||||
public:
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::can::IRegisterReceiver)
|
||||
SDV_INTERFACE_ENTRY(sdv::can::ISend)
|
||||
SDV_INTERFACE_ENTRY(sdv::can::IInformation)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::Device)
|
||||
DECLARE_OBJECT_CLASS_NAME("CAN_Com_SilKit")
|
||||
DECLARE_DEFAULT_OBJECT_NAME("CAN_Communication_Object")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
virtual void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
virtual sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
virtual void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Register a CAN message receiver. Overload of sdv::can::IRegisterReceiver::RegisterReceiver.
|
||||
* @param[in] pReceiver Pointer to the receiver interface.
|
||||
*/
|
||||
virtual void RegisterReceiver(/*in*/ sdv::can::IReceive* pReceiver) override;
|
||||
|
||||
/**
|
||||
* @brief Unregister a previously registered CAN message receiver. Overload of
|
||||
* sdv::can::IRegisterReceiver::UnregisterReceiver.
|
||||
* @param[in] pReceiver Pointer to the receiver interface.
|
||||
*/
|
||||
virtual void UnregisterReceiver(/*in*/ sdv::can::IReceive* pReceiver) override;
|
||||
|
||||
/**
|
||||
* @brief Send a CAN message. Overload of sdv::can::ISend::Send.
|
||||
* @param[in] sSDVCanMessage Message that is to be sent. The source node information is ignored. The target node determines over
|
||||
* what interface the message will be sent.
|
||||
* @param[in] uiIfcIndex Interface index to use for sending.
|
||||
*/
|
||||
virtual void Send(/*in*/ const sdv::can::SMessage& sSDVCanMessage, /*in*/ uint32_t uiIfcIndex) override;
|
||||
|
||||
/**
|
||||
* @brief Get a list of interface names. Overload of sdv::can::IInformation::GetInterfaces.
|
||||
* @return Sequence containing the names of the interfaces.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::u8string> GetInterfaces() const override;
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* @brief Acknowledge struct used to synchronize between Transmit and Acknowledge callback.
|
||||
*/
|
||||
struct SAcknowledgeSync : public SilKit::Services::Can::CanFrameTransmitEvent
|
||||
{
|
||||
// False positive warning of CppCheck concerning the initialization of member variables. Suppress warning.
|
||||
// cppcheck-suppress uninitDerivedMemberVar
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
SAcknowledgeSync() : SilKit::Services::Can::CanFrameTransmitEvent{} {}
|
||||
|
||||
/**
|
||||
* @brief Mutex used for synchronization.
|
||||
*/
|
||||
std::mutex mtx;
|
||||
|
||||
/**
|
||||
* @brief Condition variable to trigger transmission callback has been received.
|
||||
*/
|
||||
std::condition_variable cv;
|
||||
|
||||
/**
|
||||
* @brief FLag to indicate whether transmit acknowledge has been sent.
|
||||
*/
|
||||
bool bProcessed = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Reading configuration for SilKIt from JSON.
|
||||
* @param[in] ssSilKitJSONConfigContent SilKit JSON config file.
|
||||
* @return Return true if SilKIt JSON could be parsed successfully
|
||||
*/
|
||||
std::shared_ptr<SilKit::Config::IParticipantConfiguration> GetSilKitConfig(const std::string& ssSilKitJSONConfigContent);
|
||||
|
||||
/**
|
||||
* @brief Create Participant with unique name
|
||||
* @param[in] ssSilKitJSONConfigContent SilKit JSON config file.
|
||||
* @param[in] ssSilKitRegistryUri SilKit Registry URI.
|
||||
* @return SilKit::IParticipant, nullptr on failure
|
||||
*/
|
||||
std::unique_ptr<SilKit::IParticipant> CreateParticipantFromJSONConfig(const std::string& ssSilKitJSONConfigContent, const std::string& ssSilKitRegistryUri);
|
||||
|
||||
/**
|
||||
* @brief Create SilKit can controller.
|
||||
* @param[in] ssSilKitNetwork SilKit network.
|
||||
* @return SilKit::Services::Can::ICanController, nullptr on failure.
|
||||
*/
|
||||
SilKit::Services::Can::ICanController* CreateController(const std::string& ssSilKitNetwork);
|
||||
|
||||
/**
|
||||
* @brief Validate if the configuration includes all required settings
|
||||
* @param[in] ssSilKitJSONConfigContent SilKit JSON config file.
|
||||
* @param[in] ssSilKitNetwork Declaration of SilKit CAN network.
|
||||
* @param[in] ssSilKitRegistryUri SilKit Registry URI.
|
||||
* @return Return true if required settings are available
|
||||
*/
|
||||
bool ValidateConfiguration(const std::string& ssSilKitJSONConfigContent, const std::string& ssSilKitNetwork, const std::string& ssSilKitRegistryUri);
|
||||
|
||||
/**
|
||||
* @brief Function for SilKit Timesyncservice creation and to set simulation step handler.
|
||||
*/
|
||||
void SetupTimeSyncService();
|
||||
|
||||
/**
|
||||
* @brief Function to setup CAN interfaces.
|
||||
* @param[in] ssSilKitJSONConfigContent SilKit JSON config file.
|
||||
* @param[in] ssSilKitNetwork Declaration of SilKit CAN network.
|
||||
* @param[in] ssSilKitRegistryUri SilKit Registry URI.
|
||||
* @return Return true if CAN interfaces are setup successfully
|
||||
*/
|
||||
bool CreateSilKitConnection(const std::string& ssSilKitJSONConfigContent, const std::string& ssSilKitNetwork, const std::string& ssSilKitRegistryUri);
|
||||
|
||||
/**
|
||||
* @brief Create lifecycle service.
|
||||
* @return Return SilKit lifecycle service.
|
||||
*/
|
||||
SilKit::Services::Orchestration::ILifecycleService* CreateSilKitLifecycleService();
|
||||
|
||||
/**
|
||||
* @brief Set all SillKit handler functiones
|
||||
* @return Return SilKit lifecycle service.
|
||||
*/
|
||||
bool SetHandlerFunctions(SilKit::Services::Orchestration::ILifecycleService* silKitlifeCyleService);
|
||||
|
||||
/**
|
||||
* @brief Method to receive CAN frame via SilKit
|
||||
* @param[in] rsSilKitCanFrame CAN frame in SilKit format.
|
||||
*/
|
||||
void SilKitReceiveMessageHandler(const SilKit::Services::Can::CanFrame& rsSilKitCanFrame);
|
||||
|
||||
/**
|
||||
* @brief Method to transmit acknowledgement callback.
|
||||
* @param[in] rsSilKitTransmitAcknowledge SilKit CAN message transmit acknowledgement.
|
||||
*/
|
||||
void SilKitTransmitAcknowledgeHandler(const SilKit::Services::Can::CanFrameTransmitEvent& rsSilKitTransmitAcknowledge);
|
||||
|
||||
std::mutex m_ReceiversMtx; ///< Protect the receiver set.
|
||||
std::set<sdv::can::IReceive*> m_SetReceivers; ///< Set with receiver interfaces.
|
||||
|
||||
std::queue<sdv::can::SMessage> m_MessageQueue; ///< Map of the messages to be sent on SilKit.
|
||||
std::mutex m_QueueMutex; ///< Protection for message map.
|
||||
|
||||
SilKit::Services::Orchestration::ILifecycleService* m_SilKitLifeCycleService = nullptr; ///< SilKit lifecycle service.
|
||||
SilKit::Services::Can::ICanController* m_SilKitCanController = nullptr; ///< SilKit CAN1 Controller interface.
|
||||
sdv::core::ITimerSimulationStep* m_TimerSimulationStep = nullptr; ///< Timer simulation step.
|
||||
std::unique_ptr<SilKit::IParticipant> m_SilKitParticipant = nullptr; ///< SilKit participant.
|
||||
std::string m_SilKitParticipantName; ///< Configured SilKit participants.
|
||||
bool m_SilKitIsSynchronousMode = false; ///< SilKit sync mode when true.
|
||||
|
||||
uint32_t m_maxCanDataLength = 8; ///< maximum size of the CAN message.
|
||||
std::atomic<sdv::EObjectStatus> m_eStatus = sdv::EObjectStatus::initialization_pending; ///< Object status.
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT(CCANSilKit)
|
||||
|
||||
#endif // ! defined CAN_COM_SILKIT_H
|
||||
16
sdv_services/can_communication_sim/CMakeLists.txt
Normal file
16
sdv_services/can_communication_sim/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
# Define project
|
||||
project(can_com_sim VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(can_com_sim SHARED "can_com_sim.h" "can_com_sim.cpp")
|
||||
target_link_options(can_com_sim PRIVATE)
|
||||
target_link_libraries(can_com_sim ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
set_target_properties(can_com_sim PROPERTIES PREFIX "")
|
||||
set_target_properties(can_com_sim PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(can_com_sim CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} can_com_sim PARENT_SCOPE)
|
||||
192
sdv_services/can_communication_sim/can_com_sim.cpp
Normal file
192
sdv_services/can_communication_sim/can_com_sim.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
#include "can_com_sim.h"
|
||||
#include <support/toml.h>
|
||||
#include "../../global/ascformat/ascreader.cpp"
|
||||
#include "../../global/ascformat/ascwriter.cpp"
|
||||
|
||||
CCANSimulation::CCANSimulation()
|
||||
{}
|
||||
|
||||
CCANSimulation::~CCANSimulation()
|
||||
{}
|
||||
|
||||
void CCANSimulation::Initialize(const sdv::u8string& rssObjectConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
sdv::toml::CTOMLParser config(rssObjectConfig.c_str());
|
||||
auto nodeSource = config.GetDirect("Source");
|
||||
if (nodeSource.GetType() == sdv::toml::ENodeType::node_string)
|
||||
m_pathSource = nodeSource.GetValue();
|
||||
auto nodeTarget = config.GetDirect("Target");
|
||||
if (nodeTarget.GetType() == sdv::toml::ENodeType::node_string)
|
||||
m_pathTarget = nodeTarget.GetValue();
|
||||
if (m_pathSource.empty() && m_pathTarget.empty())
|
||||
{
|
||||
SDV_LOG(sdv::core::ELogSeverity::error,
|
||||
"At least the source or the target ASC files must be specified.");
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
}
|
||||
else if (m_pathSource == m_pathTarget)
|
||||
{
|
||||
SDV_LOG(sdv::core::ELogSeverity::error,
|
||||
"Source and target ASC files '" + m_pathSource.generic_u8string() + "' cannot be the same.");
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
}
|
||||
else if (std::filesystem::exists(m_pathTarget))
|
||||
{
|
||||
SDV_LOG(sdv::core::ELogSeverity::warning,
|
||||
"Target ASC file '" + m_pathSource.generic_u8string() + "' will be overwritten.");
|
||||
}
|
||||
else if (m_pathSource.empty() && m_pathTarget.empty())
|
||||
{
|
||||
SDV_LOG(sdv::core::ELogSeverity::error, "No ASC file configured for reading or writing.");
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
}
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException& e)
|
||||
{
|
||||
SDV_LOG(sdv::core::ELogSeverity::error, "Configuration could not be read: ", e.what());
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
}
|
||||
catch (const std::runtime_error& e)
|
||||
{
|
||||
SDV_LOG(sdv::core::ELogSeverity::error, "Configuration could not be read: ", e.what());
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
}
|
||||
if (m_eStatus == sdv::EObjectStatus::initialization_failure) return;
|
||||
|
||||
// Initialize the ASC writer
|
||||
if (!m_pathTarget.empty())
|
||||
SDV_LOG(sdv::core::ELogSeverity::info,
|
||||
"CAN simulator uses ASC file '" + m_pathTarget.generic_u8string() + "' to record CAN data.");
|
||||
m_writer.StartTimer();
|
||||
|
||||
// Initialize the ASC reader
|
||||
if (!m_pathSource.empty())
|
||||
SDV_LOG(sdv::core::ELogSeverity::info,
|
||||
"CAN simulator uses ASC file '" + m_pathSource.generic_u8string() + "' to playback CAN data.");
|
||||
if (!m_pathSource.empty() && !m_reader.Read(m_pathSource))
|
||||
{
|
||||
SDV_LOG(sdv::core::ELogSeverity::error,
|
||||
"Failed to read ASC file '" + m_pathSource.generic_u8string() + "' for CAN playback.");
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the status
|
||||
m_eStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CCANSimulation::GetStatus() const
|
||||
{
|
||||
return m_eStatus;
|
||||
}
|
||||
|
||||
void CCANSimulation::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eStatus == sdv::EObjectStatus::running || m_eStatus == sdv::EObjectStatus::initialized)
|
||||
{
|
||||
m_eStatus = sdv::EObjectStatus::configuring;
|
||||
|
||||
// Stop playback
|
||||
m_reader.StopPlayback();
|
||||
}
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eStatus == sdv::EObjectStatus::configuring || m_eStatus == sdv::EObjectStatus::initialized)
|
||||
{
|
||||
m_eStatus = sdv::EObjectStatus::running;
|
||||
|
||||
// Start playback
|
||||
m_reader.StartPlayback([&](const asc::SCanMessage& rsMsg) { PlaybackFunc(rsMsg); });
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CCANSimulation::Shutdown()
|
||||
{
|
||||
m_eStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
|
||||
// Stop playback
|
||||
m_reader.StopPlayback();
|
||||
|
||||
// Write the recording
|
||||
if (m_writer.HasSamples() && !m_pathTarget.empty())
|
||||
{
|
||||
if (!m_writer.Write(m_pathTarget))
|
||||
SDV_LOG(sdv::core::ELogSeverity::error,
|
||||
"Failed to write ASC file '" + m_pathTarget.generic_u8string() + "' with CAN recording.");
|
||||
}
|
||||
|
||||
// Update the status
|
||||
m_eStatus = sdv::EObjectStatus::destruction_pending;
|
||||
}
|
||||
|
||||
void CCANSimulation::RegisterReceiver(/*in*/ sdv::can::IReceive* pReceiver)
|
||||
{
|
||||
if (m_eStatus != sdv::EObjectStatus::configuring) return;
|
||||
if (!pReceiver) return;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxReceivers);
|
||||
m_setReceivers.insert(pReceiver);
|
||||
}
|
||||
|
||||
void CCANSimulation::UnregisterReceiver(/*in*/ sdv::can::IReceive* pReceiver)
|
||||
{
|
||||
// NOTE: Normally the remove function should be called in the configuration mode. Since it doesn't give
|
||||
// feedback and the associated caller might delete any receiving function, allow the removal to take place even
|
||||
// when running.
|
||||
|
||||
if (!pReceiver) return;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxReceivers);
|
||||
m_setReceivers.erase(pReceiver);
|
||||
}
|
||||
|
||||
void CCANSimulation::Send(/*in*/ const sdv::can::SMessage& sMsg, /*in*/ uint32_t uiIfcIndex)
|
||||
{
|
||||
if (m_eStatus != sdv::EObjectStatus::running) return;
|
||||
|
||||
asc::SCanMessage sAscCan{};
|
||||
sAscCan.uiChannel = uiIfcIndex + 1;
|
||||
sAscCan.uiId = sMsg.uiID;
|
||||
sAscCan.bExtended = sMsg.bExtended;
|
||||
sAscCan.bCanFd = sMsg.bCanFd;
|
||||
sAscCan.eDirection = asc::SCanMessage::EDirection::tx;
|
||||
sAscCan.uiLength = static_cast<uint32_t>(sMsg.seqData.length());
|
||||
std::copy_n(sMsg.seqData.begin(), sMsg.seqData.length(), std::begin(sAscCan.rguiData));
|
||||
m_writer.AddSample(sAscCan);
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::u8string> CCANSimulation::GetInterfaces() const
|
||||
{
|
||||
sdv::sequence<sdv::u8string> seqIfcNames;
|
||||
if (m_eStatus != sdv::EObjectStatus::running) return seqIfcNames;
|
||||
std::unique_lock<std::mutex> lock(m_mtxInterfaces);
|
||||
for (const auto& rprInterface : m_vecInterfaces)
|
||||
seqIfcNames.push_back(rprInterface.second);
|
||||
return seqIfcNames;
|
||||
}
|
||||
|
||||
void CCANSimulation::PlaybackFunc(const asc::SCanMessage& rsMsg)
|
||||
{
|
||||
if (m_eStatus != sdv::EObjectStatus::running) return;
|
||||
|
||||
// Create sdv CAN message
|
||||
sdv::can::SMessage sSdvCan{};
|
||||
sSdvCan.uiID = rsMsg.uiId;
|
||||
sSdvCan.bExtended = rsMsg.bExtended;
|
||||
sSdvCan.bCanFd = rsMsg.bCanFd;
|
||||
sSdvCan.seqData = sdv::sequence<uint8_t>(std::begin(rsMsg.rguiData), std::begin(rsMsg.rguiData) + rsMsg.uiLength);
|
||||
|
||||
// Distribute the CAN message to all receivers
|
||||
std::unique_lock<std::mutex> lock(m_mtxReceivers);
|
||||
for (sdv::can::IReceive* pReceiver : m_setReceivers)
|
||||
pReceiver->Receive(sSdvCan, rsMsg.uiChannel - 1);
|
||||
}
|
||||
116
sdv_services/can_communication_sim/can_com_sim.h
Normal file
116
sdv_services/can_communication_sim/can_com_sim.h
Normal file
@@ -0,0 +1,116 @@
|
||||
#ifndef CAN_COM_SIMULATION_H
|
||||
#define CAN_COM_SIMULATION_H
|
||||
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
#include <interfaces/can.h>
|
||||
#include <support/component_impl.h>
|
||||
#include "../../global/ascformat/ascreader.h"
|
||||
#include "../../global/ascformat/ascwriter.h"
|
||||
|
||||
/**
|
||||
* @brief Component to establish Socket CAN communication between VAPI and external application
|
||||
*/
|
||||
class CCANSimulation : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::can::IRegisterReceiver,
|
||||
public sdv::can::ISend, sdv::can::IInformation
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CCANSimulation();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
virtual ~CCANSimulation() override;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::can::IRegisterReceiver)
|
||||
SDV_INTERFACE_ENTRY(sdv::can::ISend)
|
||||
SDV_INTERFACE_ENTRY(sdv::can::IInformation)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::Device)
|
||||
DECLARE_OBJECT_CLASS_NAME("CAN_Com_Sim")
|
||||
DECLARE_DEFAULT_OBJECT_NAME("CAN_Communication_Object")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
virtual void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
virtual sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
virtual void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Register a CAN message receiver. Overload of sdv::can::IRegisterReceiver::RegisterReceiver.
|
||||
* @param[in] pReceiver Pointer to the receiver interface.
|
||||
*/
|
||||
virtual void RegisterReceiver(/*in*/ sdv::can::IReceive* pReceiver) override;
|
||||
|
||||
/**
|
||||
* @brief Unregister a previously registered CAN message receiver. Overload of
|
||||
* sdv::can::IRegisterReceiver::UnregisterReceiver.
|
||||
* @param[in] pReceiver Pointer to the receiver interface.
|
||||
*/
|
||||
virtual void UnregisterReceiver(/*in*/ sdv::can::IReceive* pReceiver) override;
|
||||
|
||||
/**
|
||||
* @brief Send a CAN message. Overload of sdv::can::ISend::Send.
|
||||
* @param[in] sMsg Message that is to be sent. The source node information is ignored. The target node determines over
|
||||
* what interface the message will be sent.
|
||||
* @param[in] uiIfcIndex Interface index to use for sending.
|
||||
*/
|
||||
virtual void Send(/*in*/ const sdv::can::SMessage& sMsg, /*in*/ uint32_t uiIfcIndex) override;
|
||||
|
||||
/**
|
||||
* @brief Get a list of interface names. Overload of sdv::can::IInformation::GetInterfaces.
|
||||
* @return Sequence containing the names of the interfaces.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::u8string> GetInterfaces() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Playback function for ASC data playback.
|
||||
*/
|
||||
void PlaybackFunc(const asc::SCanMessage& rsMsg);
|
||||
|
||||
std::atomic<sdv::EObjectStatus> m_eStatus = sdv::EObjectStatus::initialization_pending; ///< Object status
|
||||
std::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.
|
||||
std::map<int, size_t> m_mapIfc2Idx; ///< Map with interface to index.
|
||||
std::vector<std::pair<int, std::string>> m_vecInterfaces; ///< Vector with interfaces.
|
||||
std::filesystem::path m_pathSource; ///< Path to the source ASC file.
|
||||
std::filesystem::path m_pathTarget; ///< Path to the target ASC file.
|
||||
asc::CAscReader m_reader; ///< Reader for ASC file playback.
|
||||
asc::CAscWriter m_writer; ///< Writer for ASC file recording.
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT(CCANSimulation)
|
||||
|
||||
#endif // ! defined CAN_COM_SIMULATION_H
|
||||
23
sdv_services/can_communication_socket_can/CMakeLists.txt
Normal file
23
sdv_services/can_communication_socket_can/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
# Build for linux only
|
||||
if(UNIX)
|
||||
|
||||
# Define project
|
||||
project(can_com_sockets VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Copy the config files
|
||||
file (COPY ${PROJECT_SOURCE_DIR}/config/test_can_communication_sockets.toml DESTINATION ${CMAKE_BINARY_DIR}/tests/bin/config/)
|
||||
|
||||
# Define target
|
||||
add_library(can_com_sockets SHARED "can_com_sockets.h" "can_com_sockets.cpp")
|
||||
target_link_libraries(can_com_sockets ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_link_options(can_com_sockets PRIVATE)
|
||||
|
||||
set_target_properties(can_com_sockets PROPERTIES PREFIX "")
|
||||
set_target_properties(can_com_sockets PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(can_com_sockets CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} can_com_sockets PARENT_SCOPE)
|
||||
endif()
|
||||
339
sdv_services/can_communication_socket_can/can_com_sockets.cpp
Normal file
339
sdv_services/can_communication_socket_can/can_com_sockets.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
#include "can_com_sockets.h"
|
||||
|
||||
void CCANSockets::Initialize(const sdv::u8string& rssObjectConfig)
|
||||
{
|
||||
std::deque<std::string> vecConfigInterfaces;
|
||||
try
|
||||
{
|
||||
sdv::toml::CTOMLParser config(rssObjectConfig.c_str());
|
||||
sdv::toml::CNodeCollection nodeSource = config.GetDirect("canSockets");
|
||||
if (nodeSource.GetType() == sdv::toml::ENodeType::node_array)
|
||||
{
|
||||
for (size_t nIndex = 0; nIndex < nodeSource.GetCount(); nIndex++)
|
||||
{
|
||||
sdv::toml::CNode nodeInterface = nodeSource[nIndex];
|
||||
if (nodeInterface.GetType() == sdv::toml::ENodeType::node_string)
|
||||
{
|
||||
std::string ssIfcName = nodeInterface.GetValue();
|
||||
if (!ssIfcName.empty())
|
||||
{
|
||||
vecConfigInterfaces.push_back(ssIfcName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vecConfigInterfaces.size() == 0)
|
||||
{
|
||||
auto node = config.GetDirect("canSockets");
|
||||
if (node.GetType() == sdv::toml::ENodeType::node_string)
|
||||
{
|
||||
vecConfigInterfaces.push_back(node.GetValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (const sdv::toml::XTOMLParseException& e)
|
||||
{
|
||||
SDV_LOG_ERROR("Configuration could not be read: ", e.what());
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
if (vecConfigInterfaces.size() == 0)
|
||||
{
|
||||
SDV_LOG_WARNING("Configuration failure, no 'canSockets' param found");
|
||||
}
|
||||
|
||||
if (!SetupCANSockets(vecConfigInterfaces))
|
||||
{
|
||||
m_eStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
m_threadReceive = std::thread(&CCANSockets::ReceiveThreadFunc, this);
|
||||
|
||||
m_eStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
bool CCANSockets::SetupCANSockets(const std::deque<std::string>& vecConfigInterfaces)
|
||||
{
|
||||
// Retrieve the list of available interfaces
|
||||
std::set<std::string> availableInterfaces;
|
||||
struct ifaddrs *ifaddr, *ifa;
|
||||
if (getifaddrs(&ifaddr) != -1)
|
||||
{
|
||||
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
|
||||
{
|
||||
availableInterfaces.insert(ifa->ifa_name);
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifaddr);
|
||||
|
||||
CreateAndBindSockets(vecConfigInterfaces, availableInterfaces);
|
||||
|
||||
bool isSocketCreated = false;
|
||||
std::unique_lock<std::mutex> lock(m_mtxSockets);
|
||||
for (auto socket : m_vecSockets)
|
||||
{
|
||||
if ((socket.localSocket > 0) && (socket.networkInterface > 0))
|
||||
{
|
||||
isSocketCreated = true;
|
||||
SDV_LOG_INFO("Enabling CAN interface: ", socket.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSocketCreated)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SDV_LOG_ERROR(" Error: Socket list is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
void CCANSockets::CreateAndBindSockets(const std::deque<std::string>& vecConfigInterfaces,
|
||||
const std::set<std::string>& availableInterfaces)
|
||||
{
|
||||
for (const auto& configInterface : vecConfigInterfaces)
|
||||
{
|
||||
SSocketDefinition socketDef;
|
||||
socketDef.name = "";
|
||||
socketDef.networkInterface = 0;
|
||||
socketDef.localSocket = -1;
|
||||
|
||||
// Create a socket
|
||||
int localSocket = socket(PF_CAN, SOCK_RAW, CAN_RAW);
|
||||
if (localSocket == -1)
|
||||
{
|
||||
SDV_LOG_ERROR("Error creating Socket");
|
||||
return;
|
||||
}
|
||||
|
||||
if(availableInterfaces.find(configInterface) == availableInterfaces.end())
|
||||
{
|
||||
SDV_LOG_WARNING("Interface not found: ", configInterface);
|
||||
std::unique_lock<std::mutex> lock(m_mtxSockets);
|
||||
m_vecSockets.push_back(socketDef);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto ifIndex = if_nametoindex(configInterface.c_str());
|
||||
socketDef.networkInterface = ifIndex;
|
||||
|
||||
// Bind the socket to the specified CAN interface
|
||||
sockaddr_can sAddr = {};
|
||||
sAddr.can_family = AF_CAN;
|
||||
sAddr.can_ifindex = ifIndex;
|
||||
if (bind(localSocket, reinterpret_cast<sockaddr*>(&sAddr), sizeof(sAddr)) == -1)
|
||||
{
|
||||
SDV_LOG_ERROR("Error binding socket to interface ", configInterface);
|
||||
std::unique_lock<std::mutex> lock(m_mtxSockets);
|
||||
m_vecSockets.push_back(socketDef);
|
||||
close(localSocket);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set socket to be non-blocking
|
||||
if (fcntl(localSocket, F_SETFL, O_NONBLOCK) == -1)
|
||||
{
|
||||
perror("fcntl - F_SETFL");
|
||||
SDV_LOG_ERROR("Error setting socket to be non-blocking:", configInterface);
|
||||
close(localSocket);
|
||||
std::unique_lock<std::mutex> lock(m_mtxSockets);
|
||||
m_vecSockets.push_back(socketDef);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store the successfully configured socket
|
||||
socketDef.name = configInterface;
|
||||
socketDef.localSocket = localSocket;
|
||||
std::unique_lock<std::mutex> lock(m_mtxSockets);
|
||||
m_vecSockets.push_back(socketDef);
|
||||
}
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CCANSockets::GetStatus() const
|
||||
{
|
||||
return m_eStatus;
|
||||
}
|
||||
|
||||
void CCANSockets::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eStatus == sdv::EObjectStatus::running || m_eStatus == sdv::EObjectStatus::initialized)
|
||||
m_eStatus = sdv::EObjectStatus::configuring;
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eStatus == sdv::EObjectStatus::configuring || m_eStatus == sdv::EObjectStatus::initialized)
|
||||
m_eStatus = sdv::EObjectStatus::running;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CCANSockets::Shutdown()
|
||||
{
|
||||
m_eStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
|
||||
// Wait until the receiving thread is finished.
|
||||
if (m_threadReceive.joinable())
|
||||
m_threadReceive.join();
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSockets);
|
||||
for (const auto& socket : m_vecSockets)
|
||||
{
|
||||
if (socket.localSocket > 0)
|
||||
{
|
||||
close(socket.localSocket);
|
||||
}
|
||||
}
|
||||
|
||||
m_eStatus = sdv::EObjectStatus::destruction_pending;
|
||||
}
|
||||
|
||||
void CCANSockets::RegisterReceiver(/*in*/ sdv::can::IReceive* pReceiver)
|
||||
{
|
||||
if (m_eStatus != sdv::EObjectStatus::configuring) return;
|
||||
if (!pReceiver) return;
|
||||
|
||||
SDV_LOG_INFO("Registering VAPI CAN communication receiver...");
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxReceivers);
|
||||
m_setReceivers.insert(pReceiver);
|
||||
}
|
||||
|
||||
void CCANSockets::UnregisterReceiver(/*in*/ sdv::can::IReceive* pReceiver)
|
||||
{
|
||||
// NOTE: Normally the remove function should be called in the configuration mode. Since it doesn't give
|
||||
// feedback and the associated caller might delete any receiving function, allow the removal to take place even
|
||||
// when running.
|
||||
|
||||
if (!pReceiver) return;
|
||||
|
||||
SDV_LOG_INFO("Unregistering VAPI CAN communication receiver...");
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxReceivers);
|
||||
m_setReceivers.erase(pReceiver);
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::u8string> CCANSockets::GetInterfaces() const
|
||||
{
|
||||
sdv::sequence<sdv::u8string> seqIfcNames;
|
||||
if (m_eStatus != sdv::EObjectStatus::running)
|
||||
{
|
||||
return seqIfcNames;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSockets);
|
||||
for (const auto& rprInterface : m_vecSockets)
|
||||
{
|
||||
if (!rprInterface.name.empty())
|
||||
{
|
||||
seqIfcNames.push_back(rprInterface.name);
|
||||
}
|
||||
}
|
||||
|
||||
return seqIfcNames;
|
||||
}
|
||||
|
||||
void CCANSockets::Send(const sdv::can::SMessage& sMsg, uint32_t uiConfigIndex)
|
||||
{
|
||||
if (m_eStatus != sdv::EObjectStatus::running) return;
|
||||
if (sMsg.bCanFd) return; // CAN-FD not supported
|
||||
if (sMsg.seqData.size() > 8) return; // Invalid message length.
|
||||
|
||||
sockaddr_can sAddr{};
|
||||
can_frame sFrame{};
|
||||
|
||||
// Convert the message structure from VAPI SMessage to SocketCAN can_frame
|
||||
memset(&sFrame,0, sizeof(sFrame));
|
||||
sFrame.can_dlc = sMsg.seqData.size();
|
||||
if (sFrame.can_dlc > 8)
|
||||
return;
|
||||
|
||||
sFrame.can_id = sMsg.uiID;
|
||||
if (sMsg.bExtended)
|
||||
{
|
||||
sFrame.can_id |= CAN_EFF_FLAG;
|
||||
}
|
||||
|
||||
for (uint32_t index = 0; index < sFrame.can_dlc; ++index)
|
||||
{
|
||||
sFrame.data[index] = sMsg.seqData[index];
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSockets);
|
||||
|
||||
auto it = m_vecSockets.begin();
|
||||
std::advance(it, uiConfigIndex);
|
||||
if (it != m_vecSockets.end())
|
||||
{
|
||||
if ((it->localSocket > 0) && (it->networkInterface > 0))
|
||||
{
|
||||
sAddr.can_ifindex = it->networkInterface;
|
||||
sAddr.can_family = AF_CAN;
|
||||
sendto(it->localSocket, &sFrame, sizeof(can_frame), 0, reinterpret_cast<sockaddr*>(&sAddr), sizeof(sAddr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CCANSockets::ReceiveThreadFunc()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
enum {retry, cont, exit} eNextStep = exit;
|
||||
switch (m_eStatus)
|
||||
{
|
||||
case sdv::EObjectStatus::configuring:
|
||||
case sdv::EObjectStatus::initialization_pending:
|
||||
case sdv::EObjectStatus::initialized:
|
||||
case sdv::EObjectStatus::initializing:
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
eNextStep = retry;
|
||||
break;
|
||||
case sdv::EObjectStatus::running:
|
||||
eNextStep = cont;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (eNextStep == exit) break;
|
||||
if (eNextStep == retry) continue;
|
||||
|
||||
sockaddr_can sAddr{};
|
||||
can_frame sFrame{};
|
||||
socklen_t nAddrLen = sizeof(sAddr);
|
||||
|
||||
for (const auto& socket : m_vecSockets)
|
||||
{
|
||||
if ((socket.localSocket > 0) && (socket.networkInterface > 0))
|
||||
{
|
||||
//auto socketIndex = socket.localSocket;
|
||||
ssize_t nBytes = recvfrom(socket.localSocket, &sFrame, sizeof(can_frame), 0, reinterpret_cast<sockaddr*>(&sAddr), &nAddrLen);
|
||||
if (nBytes < 0)
|
||||
{
|
||||
// No data received... wait for 1 ms and try again
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
sdv::can::SMessage sMsg{};
|
||||
sMsg.uiID = sFrame.can_id;
|
||||
for (size_t nDataIndex = 0; nDataIndex < static_cast<size_t>(sFrame.can_dlc); nDataIndex++)
|
||||
{
|
||||
sMsg.seqData.push_back(sFrame.data[nDataIndex]);
|
||||
}
|
||||
|
||||
// Broadcast the message to the receivers
|
||||
std::unique_lock<std::mutex> lockReceivers(m_mtxReceivers);
|
||||
for (sdv::can::IReceive* pReceiver : m_setReceivers)
|
||||
{
|
||||
pReceiver->Receive(sMsg, socket.networkInterface);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
147
sdv_services/can_communication_socket_can/can_com_sockets.h
Normal file
147
sdv_services/can_communication_socket_can/can_com_sockets.h
Normal file
@@ -0,0 +1,147 @@
|
||||
#ifndef CAN_COM_SOCKET_H
|
||||
#define CAN_COM_SOCKET_H
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/can.h>
|
||||
#include <linux/if.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <support/toml.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <interfaces/can.h>
|
||||
|
||||
#ifndef __linux__
|
||||
// cppcheck-suppress preprocessorErrorDirective
|
||||
#error This code builds only on LINUX
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Component to establish Socket CAN communication between VAPI and external application
|
||||
*/
|
||||
class CCANSockets : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::can::IRegisterReceiver,
|
||||
public sdv::can::ISend, sdv::can::IInformation
|
||||
{
|
||||
public:
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::can::IRegisterReceiver)
|
||||
SDV_INTERFACE_ENTRY(sdv::can::ISend)
|
||||
SDV_INTERFACE_ENTRY(sdv::can::IInformation)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::Device)
|
||||
DECLARE_OBJECT_CLASS_NAME("CAN_Com_Sockets")
|
||||
DECLARE_DEFAULT_OBJECT_NAME("CAN_Communication_Object")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
* The configuration contains either one interface name a list of interface names.
|
||||
* The Send() method must use the index of this list to determine the interface
|
||||
* In case of a single interface name the index is 0.
|
||||
* canSockets = "vcan0"
|
||||
* or
|
||||
* canSockets = ["vcan1", "vcan8", "vcan9", "vcan2"]
|
||||
*/
|
||||
virtual void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
virtual sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
virtual void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Register a CAN message receiver. Overload of sdv::can::IRegisterReceiver::RegisterReceiver.
|
||||
* @param[in] pReceiver Pointer to the receiver interface.
|
||||
*/
|
||||
virtual void RegisterReceiver(/*in*/ sdv::can::IReceive* pReceiver) override;
|
||||
|
||||
/**
|
||||
* @brief Unregister a previously registered CAN message receiver. Overload of
|
||||
* sdv::can::IRegisterReceiver::UnregisterReceiver.
|
||||
* @param[in] pReceiver Pointer to the receiver interface.
|
||||
*/
|
||||
virtual void UnregisterReceiver(/*in*/ sdv::can::IReceive* pReceiver) override;
|
||||
|
||||
/**
|
||||
* @brief Send a CAN message. Overload of sdv::can::ISend::Send.
|
||||
* @param[in] sMsg Message to be sent.
|
||||
* @param[in] uiConfigIndex Interface index to use for sending.
|
||||
* Must match with the configuration list. In case configuration contains a single element the index is 0.
|
||||
* The message cannot be sent to all interfaces automatically
|
||||
*/
|
||||
virtual void Send(/*in*/ const sdv::can::SMessage& sMsg, /*in*/ uint32_t uiConfigIndex) override;
|
||||
|
||||
/**
|
||||
* @brief Get a list of interface names. Overload of sdv::can::IInformation::GetInterfaces.
|
||||
* @return Sequence containing the names of the interfaces.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::u8string> GetInterfaces() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Thread function to read data from all bound interfaces.
|
||||
*/
|
||||
void ReceiveThreadFunc();
|
||||
|
||||
/**
|
||||
* @brief Function to setup the sockets in the configuration
|
||||
* @param[in] vecConfigInterfaces List of interface names which should be connected to a socket
|
||||
*/
|
||||
bool SetupCANSockets(const std::deque<std::string>& vecConfigInterfaces);
|
||||
|
||||
/**
|
||||
* @brief Function to create and setup sockets, collected in m_vecSockets
|
||||
* @param[in] vecConfigInterfaces List of interface names which should be connected to a socket
|
||||
* @param[in] availableInterfaces List of available interface names
|
||||
*/
|
||||
void CreateAndBindSockets(const std::deque<std::string>& vecConfigInterfaces,
|
||||
const std::set<std::string>& availableInterfaces);
|
||||
|
||||
/**
|
||||
* @brief Socket definition structure
|
||||
*/
|
||||
struct SSocketDefinition
|
||||
{
|
||||
int networkInterface; ///< network interface, must be > 0
|
||||
int32_t localSocket; ///< local socket id; -1 represents an invalid socket element
|
||||
std::string name; ///< interface name, can be empty in case of an invalid socket element
|
||||
};
|
||||
|
||||
std::atomic<sdv::EObjectStatus> m_eStatus = sdv::EObjectStatus::initialization_pending; ///< Object status
|
||||
std::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.
|
||||
std::deque<SSocketDefinition> m_vecSockets; ///< Socket list
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT(CCANSockets)
|
||||
|
||||
#endif // ! defined CAN_COM_SOCKET_H
|
||||
@@ -0,0 +1,19 @@
|
||||
[Configuration]
|
||||
Version = 100
|
||||
|
||||
[[Component]]
|
||||
Path = "task_timer.sdv"
|
||||
Class = "TaskTimerService"
|
||||
|
||||
[[Component]]
|
||||
Path = "data_dispatch_service.sdv"
|
||||
Class = "DataDispatchService"
|
||||
|
||||
[[Component]]
|
||||
Path = "can_com_sockets.sdv"
|
||||
Class = "CAN_Com_Sockets"
|
||||
canSockets=["llcecan0"]
|
||||
|
||||
[[Component]]
|
||||
Path = "can_dl.sdv"
|
||||
Class = "CAN_data_link"
|
||||
72
sdv_services/core/CMakeLists.txt
Normal file
72
sdv_services/core/CMakeLists.txt
Normal file
@@ -0,0 +1,72 @@
|
||||
# Define project
|
||||
project(sdv_services_core VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(core_services SHARED
|
||||
"memory.cpp"
|
||||
"memory.h"
|
||||
"sdv_core.h"
|
||||
"sdv_core.cpp"
|
||||
"module_control.h"
|
||||
"module_control.cpp"
|
||||
"app_control.h"
|
||||
"app_control.cpp"
|
||||
"repository.h"
|
||||
"repository.cpp"
|
||||
"toml_parser/character_reader_utf_8.h"
|
||||
"toml_parser/lexer_toml.h"
|
||||
"toml_parser/parser_toml.h"
|
||||
"toml_parser/parser_toml.cpp"
|
||||
"toml_parser/lexer_toml.cpp"
|
||||
"toml_parser/character_reader_utf_8.cpp"
|
||||
"toml_parser/parser_node_toml.h"
|
||||
"toml_parser/parser_node_toml.cpp"
|
||||
"toml_parser/exception.h"
|
||||
"module.cpp"
|
||||
"app_config.h"
|
||||
"app_config.cpp"
|
||||
"logger_control.h"
|
||||
"logger_control.cpp"
|
||||
"logger.h"
|
||||
"logger.cpp"
|
||||
"log_csv_writer.h"
|
||||
"log_csv_writer.cpp"
|
||||
"object_lifetime_control.h"
|
||||
"object_lifetime_control.cpp"
|
||||
"toml_parser_util.h"
|
||||
"installation_manifest.h"
|
||||
"installation_manifest.cpp"
|
||||
"local_shutdown_request.h"
|
||||
"iso_monitor.h"
|
||||
"iso_monitor.cpp"
|
||||
"installation_composer.h"
|
||||
"installation_composer.cpp"
|
||||
|
||||
)
|
||||
|
||||
# Link target
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_link_options(core_services PRIVATE)
|
||||
target_link_libraries(core_services ${CMAKE_THREAD_LIBS_INIT} stdc++fs)
|
||||
if (WIN32)
|
||||
target_link_libraries(core_services Ws2_32 Winmm Rpcrt4.lib)
|
||||
else()
|
||||
target_link_libraries(core_services ${CMAKE_DL_LIBS} rt)
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(core_services Rpcrt4.lib)
|
||||
endif()
|
||||
|
||||
set_target_properties(core_services PROPERTIES PREFIX "")
|
||||
set_target_properties(core_services PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(core_services CompileCoreIDL)
|
||||
add_dependencies(core_services hardware_ident)
|
||||
add_dependencies(core_services ipc_shared_mem)
|
||||
add_dependencies(core_services process_control)
|
||||
add_dependencies(core_services core_ps)
|
||||
add_dependencies(core_services ipc_com)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} core_services PARENT_SCOPE)
|
||||
739
sdv_services/core/app_config.cpp
Normal file
739
sdv_services/core/app_config.cpp
Normal file
@@ -0,0 +1,739 @@
|
||||
#include "app_config.h"
|
||||
#include "sdv_core.h"
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
#include <interfaces/com.h>
|
||||
#include <interfaces/process.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <support/crc.h>
|
||||
|
||||
#if __unix__
|
||||
#include <utime.h>
|
||||
#endif
|
||||
|
||||
bool CAppConfig::LoadInstallationManifests()
|
||||
{
|
||||
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication()) return false;
|
||||
|
||||
std::filesystem::path pathCore = GetCoreDirectory();
|
||||
std::filesystem::path pathExe = GetExecDirectory();
|
||||
std::filesystem::path pathInstall = GetAppControl().GetInstallDir();
|
||||
|
||||
// Load the core manifest (should work in any case).
|
||||
m_manifestCore = CInstallManifest();
|
||||
if (!m_manifestCore.Load(pathCore)) return false;
|
||||
|
||||
// Check whether the exe path is identical to the core. If so, skip the manifest. If there is no manifest, this is not an error.
|
||||
if (pathCore != pathExe)
|
||||
{
|
||||
m_manifestExe = CInstallManifest();
|
||||
m_manifestExe.Load(pathExe);
|
||||
}
|
||||
else
|
||||
m_manifestExe.Clear();
|
||||
|
||||
// Get the user manifests. If there is no manifest, this is not an error.
|
||||
for (auto const& dir_entry : std::filesystem::directory_iterator{ pathInstall })
|
||||
{
|
||||
if (!dir_entry.is_directory()) continue;
|
||||
if (!std::filesystem::exists(dir_entry.path() / "install_manifest.toml")) continue;
|
||||
CInstallManifest manifestUser;
|
||||
if (manifestUser.Load(dir_entry.path(), true))
|
||||
m_vecUserManifests.push_back(manifestUser);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAppConfig::UnloadInstallatonManifests()
|
||||
{
|
||||
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication()) return;
|
||||
|
||||
m_vecUserManifests.clear();
|
||||
m_manifestExe.Clear();
|
||||
m_manifestCore.Clear();
|
||||
}
|
||||
|
||||
sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8string& ssContent)
|
||||
{
|
||||
if (GetAppControl().GetOperationState() != sdv::app::EAppOperationState::configuring)
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
|
||||
// Reset the current baseline
|
||||
ResetConfigBaseline();
|
||||
|
||||
CParserTOML parser(ssContent);
|
||||
|
||||
// Check for config file compatibility
|
||||
auto ptrConfigVersion = parser.GetRoot().GetDirect("Configuration.Version");
|
||||
if (!ptrConfigVersion)
|
||||
{
|
||||
SDV_LOG_ERROR("Configuration version must be present in the configuration file.");
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
}
|
||||
if (ptrConfigVersion->GetValue() != SDVFrameworkInterfaceVersion)
|
||||
{
|
||||
SDV_LOG_ERROR("Incompatible configuration file version.");
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
}
|
||||
|
||||
// If this is not a main, isolated or maintenance application, load all modules in the component and module sections.
|
||||
std::map<std::filesystem::path, sdv::core::TModuleID> mapModules;
|
||||
size_t nLoadable = 0;
|
||||
size_t nNotLoadable = 0;
|
||||
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication() &&
|
||||
!GetAppControl().IsMaintenanceApplication())
|
||||
{
|
||||
// Load all modules in the component section
|
||||
auto ptrComponents = parser.GetRoot().GetDirect("Component");
|
||||
if (ptrComponents && ptrComponents->GetArray())
|
||||
{
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex);
|
||||
if (ptrComponent)
|
||||
{
|
||||
// Get the information from the component.
|
||||
std::filesystem::path pathModule;
|
||||
auto ptrModule = ptrComponent->GetDirect("Path");
|
||||
if (ptrModule) pathModule = static_cast<std::string>(ptrModule->GetValue());
|
||||
|
||||
// If there is no path, this is allowed... skip this module
|
||||
if (pathModule.empty()) continue;
|
||||
|
||||
// Load the module.
|
||||
sdv::core::TModuleID tModuleID = GetModuleControl().Load(pathModule.generic_u8string());
|
||||
if (tModuleID)
|
||||
{
|
||||
mapModules[pathModule] = tModuleID;
|
||||
nLoadable++;
|
||||
}
|
||||
else
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to load module: ", pathModule);
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load all modules from the module section.
|
||||
auto ptrModules = parser.GetRoot().GetDirect("Module");
|
||||
if (ptrModules && ptrModules->GetArray())
|
||||
{
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrModules->GetArray()->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrModule = ptrModules->GetArray()->Get(uiIndex);
|
||||
if (ptrModule)
|
||||
{
|
||||
// Get the information from the component.
|
||||
std::filesystem::path pathModule;
|
||||
auto ptrModulePath = ptrModule->GetDirect("Path");
|
||||
if (ptrModulePath) pathModule = static_cast<std::string>(ptrModulePath->GetValue());
|
||||
|
||||
if (pathModule.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Missing module path for component configuration.");
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load the module.
|
||||
sdv::core::TModuleID tModuleID = GetModuleControl().Load(pathModule.generic_u8string());
|
||||
if (tModuleID)
|
||||
{
|
||||
mapModules[pathModule] = tModuleID;
|
||||
nLoadable++;
|
||||
}
|
||||
else
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to load module: ", pathModule);
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start all components from the component section
|
||||
std::filesystem::path pathLastModule;
|
||||
auto ptrComponents = parser.GetRoot().GetDirect("Component");
|
||||
if (ptrComponents && ptrComponents->GetArray())
|
||||
{
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex);
|
||||
if (ptrComponent)
|
||||
{
|
||||
// Get the information from the component.
|
||||
std::filesystem::path pathModule;
|
||||
auto ptrModule = ptrComponent->GetDirect("Path");
|
||||
if (ptrModule) pathModule = static_cast<std::string>(ptrModule->GetValue());
|
||||
std::string ssClass;
|
||||
auto ptrClass = ptrComponent->GetDirect("Class");
|
||||
if (ptrClass) ssClass = static_cast<std::string>(ptrClass->GetValue());
|
||||
std::string ssName;
|
||||
auto ptrName = ptrComponent->GetDirect("Name");
|
||||
if (ptrName) ssName = static_cast<std::string>(ptrName->GetValue());
|
||||
|
||||
// If there is a path, store it. If there is none, take the last stored
|
||||
if (!pathModule.empty())
|
||||
pathLastModule = pathModule;
|
||||
else
|
||||
pathModule = pathLastModule;
|
||||
|
||||
if (pathModule.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Missing module path for component configuration.");
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
if (ssClass.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Missing component class name in the configuration.");
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// In case of a main, isolated or maintenance application, ignore the module. In all other cases, the module was
|
||||
// loaded; get the module ID.
|
||||
sdv::core::TObjectID tObjectID = 0;
|
||||
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication() &&
|
||||
!GetAppControl().IsMaintenanceApplication())
|
||||
{
|
||||
auto itModule = mapModules.find(pathModule);
|
||||
if (itModule == mapModules.end()) continue; // Module was not loaded before...
|
||||
tObjectID = GetRepository().CreateObjectFromModule(itModule->second, ssClass, ssName, ptrComponent->CreateTOMLText());
|
||||
}
|
||||
else
|
||||
tObjectID = GetRepository().CreateObject2(ssClass, ssName, ptrComponent->CreateTOMLText());
|
||||
|
||||
if (!tObjectID)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to load component: ", ssClass);
|
||||
nNotLoadable++;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
nLoadable++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!nNotLoadable)
|
||||
return sdv::core::EConfigProcessResult::successful;
|
||||
if (!nLoadable)
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
return sdv::core::EConfigProcessResult::partially_successful;
|
||||
}
|
||||
|
||||
sdv::core::EConfigProcessResult CAppConfig::LoadConfig(/*in*/ const sdv::u8string& ssConfigPath)
|
||||
{
|
||||
if (GetAppControl().GetOperationState() != sdv::app::EAppOperationState::configuring)
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
if (ssConfigPath.empty())
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
|
||||
std::filesystem::path pathConfig;
|
||||
if (GetAppControl().IsMainApplication())
|
||||
{
|
||||
pathConfig = GetAppControl().GetInstallDir() / static_cast<std::string>(ssConfigPath);
|
||||
if (!std::filesystem::exists(pathConfig) || !std::filesystem::is_regular_file(pathConfig))
|
||||
pathConfig.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddCurrentPath();
|
||||
|
||||
try
|
||||
{
|
||||
// Get the search paths if the module path is relative
|
||||
std::list<std::filesystem::path> lstSearchPaths;
|
||||
std::filesystem::path pathSupplied(static_cast<std::string>(ssConfigPath));
|
||||
if (pathSupplied.is_relative())
|
||||
lstSearchPaths = m_lstSearchPaths;
|
||||
|
||||
// Add an empty path to allow the OS to search when our own search paths could not find the module.
|
||||
lstSearchPaths.push_back(std::filesystem::path());
|
||||
|
||||
// Run through the search paths and try to find the config file.
|
||||
for (const std::filesystem::path& rpathDirectory : lstSearchPaths)
|
||||
{
|
||||
// Compose the path
|
||||
std::filesystem::path pathConfigTemp;
|
||||
if (rpathDirectory.is_absolute())
|
||||
pathConfigTemp = (rpathDirectory / pathSupplied).lexically_normal();
|
||||
else
|
||||
{
|
||||
if (rpathDirectory.empty())
|
||||
pathConfigTemp = pathSupplied.lexically_normal();
|
||||
else
|
||||
pathConfigTemp = (GetExecDirectory() / rpathDirectory / pathSupplied).lexically_normal();
|
||||
}
|
||||
if (std::filesystem::exists(pathConfigTemp))
|
||||
{
|
||||
pathConfig = pathConfigTemp;
|
||||
m_pathLastSearchDir = rpathDirectory;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& re)
|
||||
{
|
||||
SDV_LOG_ERROR("Supplied path to config load is not valid \"", ssConfigPath, "\": ", re.what());
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
}
|
||||
}
|
||||
if (pathConfig.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Configuration file was not found \"", ssConfigPath, "\"");
|
||||
return sdv::core::EConfigProcessResult::failed;
|
||||
}
|
||||
|
||||
std::ifstream fstream(pathConfig);
|
||||
if (!fstream.is_open()) return sdv::core::EConfigProcessResult::failed;
|
||||
std::string ssContent((std::istreambuf_iterator<char>(fstream)), std::istreambuf_iterator<char>());
|
||||
return ProcessConfig(ssContent);
|
||||
}
|
||||
|
||||
bool CAppConfig::SaveConfig(/*in*/ const sdv::u8string& ssConfigPath) const
|
||||
{
|
||||
std::filesystem::path pathConfig = static_cast<std::string>(ssConfigPath);
|
||||
if (pathConfig.is_relative())
|
||||
{
|
||||
if (GetAppControl().IsMainApplication())
|
||||
pathConfig = GetAppControl().GetInstallDir() / pathConfig;
|
||||
else
|
||||
{
|
||||
if (m_pathLastSearchDir.empty())
|
||||
pathConfig = GetExecDirectory() / pathConfig;
|
||||
else
|
||||
pathConfig = m_pathLastSearchDir / pathConfig;
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream fstream(pathConfig, std::ios::out | std::ios::trunc);
|
||||
if (!fstream.is_open())
|
||||
{
|
||||
SDV_LOG_ERROR("Cannot open config file \"", ssConfigPath, "\"");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write header
|
||||
fstream << "[Configuration]" << std::endl;
|
||||
fstream << "Version = " << SDVFrameworkInterfaceVersion << " # Configuration file version." << std::endl;
|
||||
|
||||
// Write all objects and modules
|
||||
fstream << GetRepository().SaveConfig();
|
||||
|
||||
fstream.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAppConfig::AddConfigSearchDir(/*in*/ const sdv::u8string& ssDir)
|
||||
{
|
||||
if (GetAppControl().GetOperationState() != sdv::app::EAppOperationState::configuring) return false;
|
||||
|
||||
// Add initial paths if not done so already
|
||||
AddCurrentPath();
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSearchPaths);
|
||||
|
||||
std::filesystem::path pathDir(ssDir.c_str());
|
||||
|
||||
// Relative paths are always relative of the executable
|
||||
if (pathDir.is_relative())
|
||||
pathDir = GetExecDirectory() / ssDir.c_str();
|
||||
|
||||
// Remove any indirections
|
||||
pathDir = pathDir.lexically_normal();
|
||||
|
||||
// If the current path is not a directory, it cannot be added
|
||||
if (!std::filesystem::is_directory(pathDir)) return false;
|
||||
|
||||
// Check whether the path is already in the list
|
||||
if (std::find(m_lstSearchPaths.begin(), m_lstSearchPaths.end(), pathDir) != m_lstSearchPaths.end())
|
||||
return true; // This is not an error
|
||||
|
||||
// Add the path
|
||||
m_lstSearchPaths.push_back(pathDir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAppConfig::ResetConfigBaseline()
|
||||
{
|
||||
GetRepository().ResetConfigBaseline();
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Prevent bogus warning about uninitialized memory for the variable *hFile.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 6001)
|
||||
#endif
|
||||
|
||||
bool CAppConfig::Install(/*in*/ const sdv::u8string& ssInstallName, /*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles)
|
||||
{
|
||||
// Installations can only be done in the main application.
|
||||
if (!GetAppControl().IsMainApplication()) return false;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxInstallations);
|
||||
|
||||
// Check whether the installation already exists. If so, cancel the installation.
|
||||
if (m_mapInstallations.find(ssInstallName) != m_mapInstallations.end())
|
||||
{
|
||||
throw sdv::installation::XDuplicateInstall();
|
||||
}
|
||||
|
||||
// Rollback class - automatically reverses the loading.
|
||||
class CRollback
|
||||
{
|
||||
public:
|
||||
~CRollback()
|
||||
{
|
||||
bool bSuccess = true;
|
||||
for (sdv::core::TModuleID tModuleID : m_vecLoadedModules)
|
||||
{
|
||||
try
|
||||
{
|
||||
bSuccess &= GetModuleControl().ContextUnload(tModuleID, true);
|
||||
}
|
||||
catch (const sdv::XSysExcept&)
|
||||
{
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
}
|
||||
}
|
||||
if (!bSuccess)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to rollback \"", m_pathInstallRoot, "\".");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_pathInstallRoot.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
std::filesystem::remove_all(m_pathInstallRoot);
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& rexcept)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to rollback \"", m_pathInstallRoot, "\": ", rexcept.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
void Commit()
|
||||
{
|
||||
// Do not roll back.
|
||||
m_pathInstallRoot.clear();
|
||||
m_vecLoadedModules.clear();
|
||||
}
|
||||
void SetInstallPath(const std::filesystem::path& rPathInstallDir) { m_pathInstallRoot = rPathInstallDir; }
|
||||
void AddLoadedModule(sdv::core::TModuleID tModuleID) { m_vecLoadedModules.push_back(tModuleID); }
|
||||
private:
|
||||
std::filesystem::path m_pathInstallRoot; ///< Installation root directory
|
||||
std::vector<sdv::core::TModuleID> m_vecLoadedModules; ///< loaded modules
|
||||
} rollback;
|
||||
|
||||
std::filesystem::path pathInstallation = GetAppControl().GetInstallDir() / static_cast<std::string>(ssInstallName);
|
||||
std::vector<std::filesystem::path> vecComponents;
|
||||
try
|
||||
{
|
||||
// A sub-directory with the name must not already exist, if so, delete it.
|
||||
if (std::filesystem::exists(pathInstallation))
|
||||
std::filesystem::remove_all(pathInstallation);
|
||||
|
||||
// Create a directory for the installation
|
||||
std::filesystem::create_directories(pathInstallation);
|
||||
rollback.SetInstallPath(pathInstallation);
|
||||
|
||||
// Add each file
|
||||
for (const sdv::installation::SFileDesc& rsFileDesc : seqFiles)
|
||||
{
|
||||
// File path
|
||||
std::filesystem::path pathFileDir = pathInstallation;
|
||||
//if (!rsFileDesc.ssRelativeDir.empty())
|
||||
//{
|
||||
// std::filesystem::path pathTemp = static_cast<std::string>(rsFileDesc.ssRelativeDir);
|
||||
// if (pathTemp.is_absolute() || !pathTemp.is_relative())
|
||||
// throw sdv::installation::XIncorrectPath();
|
||||
// pathFileDir /= pathTemp;
|
||||
// if (!std::filesystem::exists(pathFileDir))
|
||||
// std::filesystem::create_directories(pathFileDir);
|
||||
//}
|
||||
std::filesystem::path pathFile = pathFileDir;
|
||||
if (std::filesystem::exists(pathFile))
|
||||
throw sdv::installation::XIncorrectPath();
|
||||
|
||||
// TODO EVE
|
||||
//// Check the file CRC
|
||||
//sdv::crcCRC32C crc;
|
||||
//uint32_t uiCRC = crc.calc_checksum(rsFileDesc.ptrContent.get(), rsFileDesc.ptrContent.size());
|
||||
//if (uiCRC != rsFileDesc.uiCRC32c)
|
||||
// throw sdv::installation::XIncorrectCRC();
|
||||
|
||||
// Create the file
|
||||
std::ofstream fstream(pathFile, std::ios_base::out | std::ios_base::binary);
|
||||
if (!fstream.is_open()) throw sdv::installation::XIncorrectPath();
|
||||
if (rsFileDesc.ptrContent.size())
|
||||
fstream.write(reinterpret_cast<const char*>(rsFileDesc.ptrContent.get()), rsFileDesc.ptrContent.size());
|
||||
fstream.close();
|
||||
|
||||
// Set file times
|
||||
// Note: use dedicated OS functions. C++11 filesystem library doesn't define a proper way to convert between UNIX time
|
||||
// and the time defined by the C++11 functions. This deficit has been solved in C++20.
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows uses file access for this.
|
||||
HANDLE hFile = CreateFile(pathFile.native().c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile == nullptr || hFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\"");
|
||||
throw sdv::installation::XIncorrectPath();
|
||||
}
|
||||
|
||||
// Set the filetime on the file.
|
||||
// The filetime starts counting from 1st of January 1601. The resolution is 100ns.
|
||||
// The UNIX time starts counting from 1st of January 1970. The resolution is 1s.
|
||||
// Both times ignore leap seconds.
|
||||
// Thus the UNIX time is running 11644473600 seconds behind the filetime and is a factor 10000000 larger.
|
||||
ULARGE_INTEGER sCreateTime{}, sChangeTime{};
|
||||
sCreateTime.QuadPart = (rsFileDesc.uiCreationDate + 11644473600ull) * 10000000ull;
|
||||
FILETIME ftCreateTime{ sCreateTime.LowPart, sChangeTime.HighPart };
|
||||
sChangeTime.QuadPart = (rsFileDesc.uiChangeDate + 11644473600ull) * 10000000ull;
|
||||
FILETIME ftChangeTime{ sChangeTime.LowPart, sChangeTime.HighPart };
|
||||
BOOL bRes = SetFileTime(hFile, &ftCreateTime, nullptr, &ftChangeTime);
|
||||
|
||||
// Close our handle.
|
||||
CloseHandle(hFile);
|
||||
|
||||
if (!bRes)
|
||||
{
|
||||
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\"");
|
||||
throw sdv::installation::XIncorrectPath();
|
||||
}
|
||||
#elif defined __unix__
|
||||
// Note: POSIX doesn't define the creation time.
|
||||
utimbuf sTimes{};
|
||||
sTimes.actime = std::time(0);
|
||||
sTimes.modtime = static_cast<time_t>(rsFileDesc.uiChangeDate);
|
||||
if (utime(pathFile.native().c_str(), &sTimes))
|
||||
{
|
||||
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\"");
|
||||
throw sdv::installation::XIncorrectPath();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set attributes
|
||||
if (rsFileDesc.bAttrExecutable)
|
||||
std::filesystem::permissions(pathFile, std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec |
|
||||
std::filesystem::perms::others_exec, std::filesystem::perm_options::add);
|
||||
if (rsFileDesc.bAttrReadonly)
|
||||
std::filesystem::permissions(pathFile, std::filesystem::perms::owner_write | std::filesystem::perms::group_write |
|
||||
std::filesystem::perms::others_write, std::filesystem::perm_options::remove);
|
||||
|
||||
// Component paths are stored
|
||||
//if (rsFileDesc.bAttrComponent) vecComponents.push_back(pathFile);
|
||||
}
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& rexcept)
|
||||
{
|
||||
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\": ", rexcept.what());
|
||||
throw sdv::installation::XIncorrectPath();
|
||||
}
|
||||
|
||||
// TODO: Restriction. Only complex services will be started dynamically and will be added to the configuration. Utilities will
|
||||
// also be installed but will not be started. Devices and basic services will not be available this way.
|
||||
|
||||
// TODO:
|
||||
// For each component, get the manifest using the manifest helper utility.
|
||||
// Note: to get the manifest, the component module needs to be loaded and the GetManifest exported function is called. Loading
|
||||
// a component module causes part of the component to start already and dependencies to be resolved. If this malfunctions, the
|
||||
// component could crash or do other malicious things. OR if on purpose, an application or complex service component, that
|
||||
// normally would run in an isolated environment, gets access to the memory and everything else within the process. If this
|
||||
// module is not isolated from the core, it could access the core and do nasty things. THEREFORE, the manifest helper utility is
|
||||
// started as a component in its own isolated environment and then loading the component. No component code is executed within
|
||||
// the core process.
|
||||
sdv::TObjectPtr ptrManifestUtil = sdv::core::CreateUtility("ManifestHelperUtility");
|
||||
sdv::helper::IModuleManifestHelper* pManifestHelper = ptrManifestUtil.GetInterface<sdv::helper::IModuleManifestHelper>();
|
||||
if (!pManifestHelper)
|
||||
{
|
||||
SDV_LOG_ERROR("Manifest helper utility cannot be loaded.");
|
||||
throw sdv::installation::XComponentNotLoadable();
|
||||
}
|
||||
|
||||
// Get the manifest of all components
|
||||
std::map<std::filesystem::path, sdv::u8string> mapComponentManifests;
|
||||
for (const std::filesystem::path& rpathComponent : vecComponents)
|
||||
{
|
||||
sdv::u8string ssManifest = pManifestHelper->ReadModuleManifest(rpathComponent.generic_u8string());
|
||||
if (!ssManifest.empty())
|
||||
mapComponentManifests[rpathComponent] = ssManifest;
|
||||
}
|
||||
|
||||
// Check whether there ary any manifests
|
||||
if (mapComponentManifests.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("No component in installation", ssInstallName, ".");
|
||||
throw sdv::installation::XNoManifest();
|
||||
}
|
||||
|
||||
// Load the modules
|
||||
bool bSuccess = true;
|
||||
for (const auto& rvtModule : mapComponentManifests)
|
||||
{
|
||||
sdv::core::TModuleID tModuleID = GetModuleControl().ContextLoad(rvtModule.first, rvtModule.second);
|
||||
if (!tModuleID)
|
||||
{
|
||||
bSuccess = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: What about the dependent modules that were started as well. They are not rolled back...
|
||||
rollback.AddLoadedModule(tModuleID);
|
||||
}
|
||||
if (!bSuccess)
|
||||
{
|
||||
SDV_LOG_ERROR("Manifest helper utility cannot be loaded.");
|
||||
throw sdv::installation::XComponentNotLoadable();
|
||||
}
|
||||
|
||||
// Add the installation to the installation map
|
||||
//SInstallation sInstallation{ seqFiles };
|
||||
//for (sdv::core::)
|
||||
//m_mapInstallations[ssInstallName] = SInstallation{ seqFiles };
|
||||
|
||||
// Commit the changes - prevent rollback
|
||||
rollback.Commit();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
bool CAppConfig::Update(/*in*/ const sdv::u8string& ssInstallName, /*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles)
|
||||
{
|
||||
// TODO: as soon as the configuration is updatable as well, the update uses the same configuration and possibly updates the
|
||||
// configuration.
|
||||
// Until the config update feature is available, the update can stop and update and start the installation by uninstall and
|
||||
// install.
|
||||
|
||||
bool bRet = Uninstall(ssInstallName);
|
||||
if (bRet) bRet = Install(ssInstallName, seqFiles);
|
||||
return bRet;
|
||||
}
|
||||
|
||||
bool CAppConfig::Uninstall(/*in*/ const sdv::u8string& /*ssInstallName*/)
|
||||
{
|
||||
// Installations can only be done in the main application.
|
||||
if (!GetAppControl().IsMainApplication()) return false;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::u8string> CAppConfig::GetInstallations() const
|
||||
{
|
||||
return sdv::sequence<sdv::u8string>();
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::installation::SFileDesc> CAppConfig::GetInstallationFiles(/*in*/ const sdv::u8string& /*ssInstallName*/) const
|
||||
{
|
||||
return sdv::sequence<sdv::installation::SFileDesc>();
|
||||
}
|
||||
|
||||
std::filesystem::path CAppConfig::FindInstalledModule(const std::filesystem::path& rpathRelModule)
|
||||
{
|
||||
// Search for the module in the following order:
|
||||
// - Core manifest
|
||||
// - Exe manifest
|
||||
// - User manifests
|
||||
|
||||
std::filesystem::path pathModule = m_manifestCore.FindModule(rpathRelModule);
|
||||
if (pathModule.empty())
|
||||
pathModule = m_manifestExe.FindModule(rpathRelModule);
|
||||
for (const CInstallManifest& rmanifest : m_vecUserManifests)
|
||||
{
|
||||
if (!pathModule.empty()) break;
|
||||
pathModule = rmanifest.FindModule(rpathRelModule);
|
||||
}
|
||||
return pathModule;
|
||||
}
|
||||
|
||||
std::string CAppConfig::FindInstalledModuleManifest(const std::filesystem::path& rpathRelModule)
|
||||
{
|
||||
// Search for the module in the following order:
|
||||
// - Core manifest
|
||||
// - Exe manifest
|
||||
// - User manifests
|
||||
|
||||
std::string ssManifest = m_manifestCore.FindModuleManifest(rpathRelModule);
|
||||
if (ssManifest.empty())
|
||||
ssManifest = m_manifestExe.FindModuleManifest(rpathRelModule);
|
||||
for (const CInstallManifest& rmanifest : m_vecUserManifests)
|
||||
{
|
||||
if (!ssManifest.empty()) break;
|
||||
ssManifest = rmanifest.FindModuleManifest(rpathRelModule);
|
||||
}
|
||||
return ssManifest;
|
||||
}
|
||||
|
||||
std::optional<CInstallManifest::SComponent> CAppConfig::FindInstalledComponent(const std::string& rssClass) const
|
||||
{
|
||||
// For main and isolated applications, check whether the module is in one of the installation manifests.
|
||||
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication()) return {};
|
||||
|
||||
// Search for the component in the following order:
|
||||
// - Core manifest
|
||||
// - Exe manifest
|
||||
// - User manifests
|
||||
auto optManifest = m_manifestCore.FindComponentByClass(rssClass);
|
||||
if (!optManifest) optManifest = m_manifestExe.FindComponentByClass(rssClass);
|
||||
for (const CInstallManifest& rmanifest : m_vecUserManifests)
|
||||
{
|
||||
if (optManifest) break;
|
||||
optManifest = rmanifest.FindComponentByClass(rssClass);
|
||||
}
|
||||
return optManifest;
|
||||
}
|
||||
|
||||
void CAppConfig::AddCurrentPath()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxSearchPaths);
|
||||
|
||||
// Do this only once.
|
||||
if (!m_lstSearchPaths.empty()) return;
|
||||
|
||||
// Add the core directory
|
||||
std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal();
|
||||
m_lstSearchPaths.push_back(pathCoreDir / "config/");
|
||||
|
||||
// Add the exe dir
|
||||
std::filesystem::path pathExeDir = GetExecDirectory().lexically_normal();
|
||||
if (pathExeDir != pathCoreDir) m_lstSearchPaths.push_back(pathExeDir / "config/");
|
||||
}
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
CAppConfig& CAppConfigService::GetAppConfig()
|
||||
{
|
||||
return ::GetAppConfig();
|
||||
}
|
||||
|
||||
bool CAppConfigService::EnableAppConfigAccess()
|
||||
{
|
||||
return GetAppControl().IsStandaloneApplication() ||
|
||||
GetAppControl().IsEssentialApplication();
|
||||
}
|
||||
|
||||
bool CAppConfigService::EnableAppInstallAccess()
|
||||
{
|
||||
return GetAppControl().IsMainApplication();
|
||||
}
|
||||
|
||||
#endif // !defined DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
299
sdv_services/core/app_config.h
Normal file
299
sdv_services/core/app_config.h
Normal file
@@ -0,0 +1,299 @@
|
||||
#ifndef APP_CONFIG_H
|
||||
#define APP_CONFIG_H
|
||||
|
||||
#include "app_control.h"
|
||||
#include "installation_manifest.h"
|
||||
#include <interfaces/config.h>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
// @cond DOXYGEN_IGNORE
|
||||
// Components are installed using two procedures:
|
||||
// Procedure 1: by loading a configuration file
|
||||
// mainly done when running standalone or in essential mode
|
||||
// Procedure 2: by installing using an interface
|
||||
// mainly done when running as server
|
||||
//
|
||||
// The installation of components must be executed using a separated process to ensure the components are not interfering with
|
||||
// the main process during installation.
|
||||
// The component information is stored in the manifest, exposed through an exported function, and read during installation and
|
||||
// instantiation.
|
||||
// Installed files have attributes: creation and change dates, executable file flag, CRC, relative sub-directory, compoonent flag.
|
||||
// The installation might need companion files to be installed in various relative sub-directories.
|
||||
// @endcond
|
||||
|
||||
/**
|
||||
* @brief Application configuration service
|
||||
* @details In the configuration system objects, devices, basic services, complex services and apps are defined and will be started
|
||||
* suring the load process. The objects are loaded in this order (system objects first, apps last) unless dependencies define
|
||||
* a different order. Utilities, proxy and stub objects are not specified here, since they can be started 'on-the-fly' if needed.
|
||||
* Loading a configuration extends the current configuration. Saving a configuration includes all objects since the last
|
||||
* configuration not including the components present before the last load.
|
||||
*
|
||||
* The configuration file is a TOML file with the following format:
|
||||
* @code
|
||||
* [Configuration]
|
||||
* Version = 100 # Configuration file version.
|
||||
*
|
||||
* [[Component]]
|
||||
* Path = "my_module.sdv # Relative to the executable or absolute path to the module - not used for main and isolated
|
||||
* # applications since the components must be installed.
|
||||
* Class = "MyComponent" # Class name of the component.
|
||||
* Name = "MyPersonalComponent" # Optional instance name of the component. If not provided, the name will automatically be the
|
||||
* # default name of the component or if not available the class name of the component.
|
||||
* AttributeXYZ = 123 # Additional settings for the component provided during initialization.
|
||||
*
|
||||
* [[Component]]
|
||||
* Class = "MyComponent2" # Class name of the component - if also present in "my_module.sdv" doesn't need additional
|
||||
* # 'Path' value. The component name is taken from the default name of the component.
|
||||
*
|
||||
* [[Component]]
|
||||
* Class = "MyComponent" # Class name of the component; class is the same as before.
|
||||
* Name = "MyPersonalComponent2" # Specific name identifying another instance of the component.
|
||||
*
|
||||
* [[Module]]
|
||||
* Path = "my_other_module.sdv # Relative to the executable or absolute path to the module - not used for main and isolated
|
||||
* # applications since the components must be installed. This path might contain components not
|
||||
* # started, but known by the repository (utilities).
|
||||
* @endcode
|
||||
*
|
||||
* For the main application there are several configuration files:
|
||||
* - Platform configuration (OS support, middleware support, vehicle bus and Ethernet interface configuration - system objects)
|
||||
* - Vehicle interface configuration (DBC, port assignments - devices)
|
||||
* - Vehicle abstraction interface configuration (basic services)
|
||||
* - Application configuration (complex services and applications)
|
||||
*
|
||||
* If components are added dynamically (or removed dynamically) they are added to and removed from the application configuration.
|
||||
* The configuration files are located in the installation directory.
|
||||
*
|
||||
* TODO: Add additional interface function: set component operation mode - config, running...
|
||||
* When switching to config mode, switch the apps, complex services, basic services, devices and system objects (in that order) to config mode.
|
||||
* When switching back to running mode, enable running mode for the system objects, devices, base services, complex services and apps (in that order).
|
||||
*
|
||||
*/
|
||||
class CAppConfig : public sdv::IInterfaceAccess, public sdv::core::IConfig, public sdv::installation::IAppInstall
|
||||
{
|
||||
public:
|
||||
CAppConfig() = default;
|
||||
|
||||
// Interface map
|
||||
// Note: only add globally accessible interfaces here (which are not limited by the type of application).
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Load the installation manifests for core, executable and user components. Main and isolated applications only.
|
||||
* @return Returns 'true when successful; 'false' when not.
|
||||
*/
|
||||
bool LoadInstallationManifests();
|
||||
|
||||
/**
|
||||
* @brief Unload all manifest during shutdown.
|
||||
*/
|
||||
void UnloadInstallatonManifests();
|
||||
|
||||
/**
|
||||
* @brief Process the provided configuration by loading modules and creating objects/stubs/proxies defined in the
|
||||
* configuration string. Processing a configuration resets the baseline before processing. Overload of
|
||||
* sdv::core::IConfig::ProcessConfig.
|
||||
* @attention Configuration changes can only occur when the system is in configuration mode.
|
||||
* @param[in] ssContent The contents of the configuration file (TOML).
|
||||
* @return Returns a config process result enum value.
|
||||
*/
|
||||
virtual sdv::core::EConfigProcessResult ProcessConfig(/*in*/ const sdv::u8string& ssContent) override;
|
||||
|
||||
/**
|
||||
* @brief Read file pointed to by the provided file path and load modules and create objects/stubs/proxies as defined
|
||||
* in the configuration file. Loading a configuration resets the baseline before processing. Overload of
|
||||
* sdv::core::IConfig::LoadConfig.
|
||||
* @attention Configuration changes can only occur when the system is in configuration mode.
|
||||
* @param[in] ssConfigPath Path to the file containing the configuration (TOML). The path can be an absolute as well as a
|
||||
* relative path. In case a relative path is provided, the current directory is searched as well as all directories
|
||||
* supplied through the AddConfigSearchDir function.
|
||||
* @return Returns a config process result enum value.
|
||||
*/
|
||||
virtual sdv::core::EConfigProcessResult LoadConfig(/*in*/ const sdv::u8string& ssConfigPath) override;
|
||||
|
||||
/**
|
||||
* @brief Save a configuration file pointed to by the provided file path. All components are saved that were added after
|
||||
* the last baseline with the configuration specific settings. Overload of sdv::core::IConfig::SaveConfig.
|
||||
* @attention Configuration changes can only occur when the system is in configuration mode.
|
||||
* @param[in] ssConfigPath Path to the file containing the configuration (TOML). The path can be an absolute as well as a
|
||||
* relative path. In case a relative path is provided, the configuration is stored relative to the executable directory.
|
||||
* @return Returns 'true' on success; 'false' otherwise.
|
||||
*/
|
||||
virtual bool SaveConfig(/*in*/ const sdv::u8string& ssConfigPath) const override;
|
||||
|
||||
/**
|
||||
* @brief Add a search path to a folder where a config file can be found. Overload of
|
||||
* sdv::core::IConfig::AddDirectorySearchDir.
|
||||
* @param[in] ssDir Relative or absolute path to an existing folder.
|
||||
* @return Returns 'true' on success; 'false' otherwise.
|
||||
*/
|
||||
virtual bool AddConfigSearchDir(/*in*/ const sdv::u8string& ssDir) override;
|
||||
|
||||
/**
|
||||
* @brief Reset the configuration baseline.
|
||||
* @details The configuration baseline determines what belongs to the current configuration. Any object being added
|
||||
* after this baseline will be stored in a configuration file.
|
||||
*/
|
||||
virtual void ResetConfigBaseline() override;
|
||||
|
||||
/**
|
||||
* @brief Make an installation onto the system. Overload of sdv::installation::IAppInstall::Install.
|
||||
* @details Make an installation with from component modules and supporting files. At least one component module must
|
||||
* be provided for this installation to be successful (component attribute flag must be set). Components are only
|
||||
* installed, not activated.
|
||||
* @remarks The system needs to run in configuration mode.
|
||||
* @param[in] ssInstallName Name of the installation. Must be unique to the system.
|
||||
* @param[in] seqFiles Files belonging to the installation. This could be component modules as well as supporting files.
|
||||
* @return Returns whether the installation was successful - installation name was unique and at least one loadable
|
||||
* component was provided.
|
||||
*/
|
||||
virtual bool Install(/*in*/ const sdv::u8string& ssInstallName,
|
||||
/*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles) override;
|
||||
|
||||
/**
|
||||
* @brief Update an installation. Overload of sdv::installation::IAppInstall::Update.
|
||||
* @details Stops a component if the component is running, makes an update and if the component was running, starts
|
||||
* the component again.
|
||||
* @todo: Currently limited to complex services only.
|
||||
* @remarks The system needs to run in configuration mode.
|
||||
* @param[in] ssInstallName Name of the installation. Must be unique to the system.
|
||||
* @param[in] seqFiles Files belonging to the installation. This could be component modules as well as supporting files.
|
||||
* @return Returns whether the installation was successful - installation name was unique and at least one loadable
|
||||
* component was provided.
|
||||
*/
|
||||
virtual bool Update(/*in*/ const sdv::u8string& ssInstallName,
|
||||
/*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles) override;
|
||||
|
||||
/**
|
||||
* @brief Uninstall of a previous installation. Overload of sdv::installation::IAppInstall::Uninstall.
|
||||
* @details Stops a component if the component is running and removes the files and modules.
|
||||
* @todo: Currently limited to complex services only.
|
||||
* @remarks The system needs to run in configuration mode.
|
||||
* @param[in] ssInstallName Installation name.
|
||||
* @return Returns whether the uninstallation was successful.
|
||||
*/
|
||||
virtual bool Uninstall(/*in*/ const sdv::u8string& ssInstallName) override;
|
||||
|
||||
/**
|
||||
* @brief Get a sequence of installations. Overload of sdv::installation::IAppInstall::GetInstallations.
|
||||
* @return The sequence with installations.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::u8string> GetInstallations() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the installed files from the installation. Overload of sdv::installation::IAppInstall::GetInstallationFiles.
|
||||
* @param[in] ssInstallName The installation to get the files for.
|
||||
* @return Sequence containing the file structures without the binary file content.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::installation::SFileDesc> GetInstallationFiles(/*in*/ const sdv::u8string& ssInstallName) const override;
|
||||
|
||||
/**
|
||||
* @brief Find the module stored in the installation manifest (core, exe or user installations).
|
||||
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
|
||||
* @return Returns the full path if the module was found or an empty path when not.
|
||||
*/
|
||||
std::filesystem::path FindInstalledModule(const std::filesystem::path& rpathRelModule);
|
||||
|
||||
/**
|
||||
* @brief Find the module stored in the installation manifest (core, exe or user installations).
|
||||
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
|
||||
* @return Returns the module manifest if the module was found or an empty string when not.
|
||||
*/
|
||||
std::string FindInstalledModuleManifest(const std::filesystem::path& rpathRelModule);
|
||||
|
||||
/**
|
||||
* @brief Search for the installed component with the specific class name.
|
||||
* @details Find the first component containing a class with the specified name. For main and isolated applications.
|
||||
* The order of checking the installation manifest is core-manifest, manifest in executable directory and manifest in supplied
|
||||
* installation directory.
|
||||
* @remarks Components of system objects specified in the user installation are not returned.
|
||||
* @param[in] rssClass Reference to the class that should be searched for. The class is compared to the class name and the
|
||||
* default name in the manifest.
|
||||
* @return Optionally returns the component manifest.
|
||||
*/
|
||||
std::optional<CInstallManifest::SComponent> FindInstalledComponent(const std::string& rssClass) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Add config folders of the core_services and the executable to the search paths if not done so already.
|
||||
*/
|
||||
void AddCurrentPath();
|
||||
|
||||
/**
|
||||
* @brief Installation structure
|
||||
*/
|
||||
struct SInstallation
|
||||
{
|
||||
sdv::sequence<sdv::installation::SFileDesc> seqFiles; ///< Companion files
|
||||
};
|
||||
|
||||
std::mutex m_mtxSearchPaths; ///< Access protection to directory list.
|
||||
std::list<std::filesystem::path> m_lstSearchPaths; ///< List of search directories.
|
||||
std::filesystem::path m_pathLastSearchDir; ///< The last search directory to also save the file to.
|
||||
std::mutex m_mtxInstallations; ///< Access protection to the installations.
|
||||
CInstallManifest m_manifestCore; ///< Install manifest for core components (main and isolated apps).
|
||||
CInstallManifest m_manifestExe; ///< Install manifest for exe components (main and isolated apps).
|
||||
std::vector<CInstallManifest> m_vecUserManifests; ///< Install manifests for user components (main and isolated apps).
|
||||
std::map<std::string, SInstallation> m_mapInstallations; ///< Installation map.
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
/**
|
||||
* @brief App config service class.
|
||||
*/
|
||||
class CAppConfigService : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CAppConfigService() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(GetAppConfig())
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableAppConfigAccess(), 1)
|
||||
SDV_INTERFACE_SECTION(1)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IConfig, GetAppConfig())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableAppInstallAccess(), 2)
|
||||
SDV_INTERFACE_SECTION(2)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::installation::IAppInstall, GetAppConfig())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("ConfigService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Get access to the application config.
|
||||
* @return Returns the one global instance of the application config.
|
||||
*/
|
||||
static CAppConfig& GetAppConfig();
|
||||
|
||||
/**
|
||||
* @brief When set, the application control access will be enabled.
|
||||
* @return Returns 'true' when the access to the application configuration is granted; otherwise returns 'false'.
|
||||
*/
|
||||
static bool EnableAppConfigAccess();
|
||||
|
||||
/**
|
||||
* @brief When set, the application install will be enabled.
|
||||
* @return Returns whether access to the application install is granted.
|
||||
*/
|
||||
static bool EnableAppInstallAccess();
|
||||
};
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CAppConfigService)
|
||||
|
||||
#endif
|
||||
|
||||
#endif // !defined APP_CONFIG_H
|
||||
975
sdv_services/core/app_control.cpp
Normal file
975
sdv_services/core/app_control.cpp
Normal file
@@ -0,0 +1,975 @@
|
||||
#include "app_control.h"
|
||||
#include "module_control.h"
|
||||
#include "repository.h"
|
||||
#include "sdv_core.h"
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
#include "../../global/base64.h"
|
||||
#include "../../global/flags.h"
|
||||
#include "../../global/tracefifo/trace_fifo.cpp"
|
||||
#include "toml_parser/parser_toml.h"
|
||||
#include "local_shutdown_request.h"
|
||||
|
||||
const char* szSettingsTemplate = R"code(# Settings file
|
||||
[Settings]
|
||||
Version = 100
|
||||
|
||||
# The system config array can contain zero or more configurations that are loaded at the time
|
||||
# the system ist started. It is advisable to split the configurations in:
|
||||
# platform config - containing all the components needed to interact with the OS,
|
||||
# middleware, vehicle bus, Ethernet.
|
||||
# vehicle interface - containing the vehicle bus interpretation components like data link
|
||||
# based on DBC and devices for their abstraction.
|
||||
# vehicle abstraction - containing the basic services
|
||||
# Load the system configurations by providing the "SystemConfig" keyword as an array of strings.
|
||||
# A relative path is relative to the installation directory (being "exe_location/instance_id").
|
||||
#
|
||||
# Example:
|
||||
# SystemConfig = [ "platform.toml", "vehicle_ifc.toml", "vehicle_abstract.toml" ]
|
||||
#
|
||||
|
||||
# The application config contains the configuration file that can be updated when services and
|
||||
# apps are being added to the system (or being removed from the system). Load the application
|
||||
# config by providing the "AppConfig" keyword as a string value. A relative path is relative to
|
||||
# the installation directory (being "exe_location/instance_id").
|
||||
#
|
||||
# Example
|
||||
# AppConfig = "app_config.toml"
|
||||
)code";
|
||||
|
||||
/**
|
||||
* @brief Specific exit handler helping to shut down after startup. In case the shutdown wasn't explicitly executed.
|
||||
*/
|
||||
void ExitHandler()
|
||||
{
|
||||
// Check if the system was properly shut down
|
||||
switch (GetAppControl().GetOperationState())
|
||||
{
|
||||
case sdv::app::EAppOperationState::shutting_down:
|
||||
case sdv::app::EAppOperationState::not_started:
|
||||
break;
|
||||
default:
|
||||
GetAppControl().Shutdown(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CAppControl::CAppControl()
|
||||
{}
|
||||
|
||||
CAppControl::~CAppControl()
|
||||
{}
|
||||
|
||||
bool CAppControl::IsMainApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::main;
|
||||
}
|
||||
|
||||
bool CAppControl::IsIsolatedApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::isolated;
|
||||
}
|
||||
|
||||
bool CAppControl::IsStandaloneApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::standalone;
|
||||
}
|
||||
|
||||
bool CAppControl::IsEssentialApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::essential;
|
||||
}
|
||||
|
||||
bool CAppControl::IsMaintenanceApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::maintenance;
|
||||
}
|
||||
|
||||
bool CAppControl::IsExternalApplication() const
|
||||
{
|
||||
return m_eContextMode == sdv::app::EAppContext::external;
|
||||
}
|
||||
|
||||
sdv::app::EAppContext CAppControl::GetContextType() const
|
||||
{
|
||||
return m_eContextMode;
|
||||
}
|
||||
|
||||
uint32_t CAppControl::GetInstanceID() const
|
||||
{
|
||||
return m_uiInstanceID;
|
||||
}
|
||||
|
||||
uint32_t CAppControl::GetRetries() const
|
||||
{
|
||||
return m_uiRetries;
|
||||
}
|
||||
|
||||
bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfaceAccess* pEventHandler)
|
||||
{
|
||||
m_pEvent = pEventHandler ? pEventHandler->GetInterface<sdv::app::IAppEvent>() : nullptr;
|
||||
|
||||
// Intercept the logging...
|
||||
std::stringstream sstreamCOUT, sstreamCLOG, sstreamCERR;
|
||||
std::streambuf* pstreambufCOUT = std::cout.rdbuf(sstreamCOUT.rdbuf());
|
||||
std::streambuf* pstreambufCLOG = std::clog.rdbuf(sstreamCLOG.rdbuf());
|
||||
std::streambuf* pstreambufCERR = std::cerr.rdbuf(sstreamCERR.rdbuf());
|
||||
|
||||
// Write initial startup message
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||||
SDV_LOG_INFO("SDV Application start at ", std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S %Z"));
|
||||
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::initializing);
|
||||
|
||||
// Process the application config.
|
||||
bool bRet = ProcessAppConfig(ssConfig);
|
||||
|
||||
// Undo logging interception
|
||||
sstreamCOUT.rdbuf()->pubsync();
|
||||
sstreamCLOG.rdbuf()->pubsync();
|
||||
sstreamCERR.rdbuf()->pubsync();
|
||||
std::cout.rdbuf(pstreambufCOUT);
|
||||
std::clog.rdbuf(pstreambufCLOG);
|
||||
std::cerr.rdbuf(pstreambufCERR);
|
||||
|
||||
// Process config error...
|
||||
if (!bRet)
|
||||
{
|
||||
std::cout << sstreamCOUT.str();
|
||||
std::clog << sstreamCLOG.str();
|
||||
std::cerr << sstreamCERR.str();
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the stream buffer and attach the streams if the application control is initialized as main app.
|
||||
if (m_eContextMode == sdv::app::EAppContext::main)
|
||||
{
|
||||
m_fifoTraceStreamBuffer.SetInstanceID(m_uiInstanceID);
|
||||
m_fifoTraceStreamBuffer.Open(1000);
|
||||
std::cout << "**********************************************" << std::endl;
|
||||
}
|
||||
|
||||
// Stream the starting logging to the output streams...
|
||||
std::cout << sstreamCOUT.str();
|
||||
std::clog << sstreamCLOG.str();
|
||||
std::cerr << sstreamCERR.str();
|
||||
|
||||
// Check for a correctly opened stream buffer
|
||||
if (m_eContextMode == sdv::app::EAppContext::main && !m_fifoTraceStreamBuffer.IsOpened())
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Log streaming could not be initialized; cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow only one instance if running as main application
|
||||
if (m_eContextMode == sdv::app::EAppContext::main)
|
||||
{
|
||||
m_pathLockFile = GetExecDirectory() / ("sdv_core_" + std::to_string(m_uiInstanceID) + ".lock");
|
||||
#ifdef _WIN32
|
||||
// Checkf or the existence of a lock file. If existing, do not continue.
|
||||
if (std::filesystem::exists(m_pathLockFile)) return false;
|
||||
|
||||
// Create temp file
|
||||
m_pLockFile = _wfopen(m_pathLockFile.native().c_str(), L"a+TD");
|
||||
#elif defined __unix__
|
||||
// Create a lock file if not existing.
|
||||
m_pLockFile = fopen(m_pathLockFile.native().c_str(), "a+");
|
||||
if (!m_pLockFile || lockf(fileno(m_pLockFile), F_TLOCK, 0) != 0)
|
||||
{
|
||||
// File exists already or could not be opened.
|
||||
// No continuation possible.
|
||||
if (m_pLockFile) fclose(m_pLockFile);
|
||||
m_pLockFile = nullptr;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string ssErrorString;
|
||||
auto fnLoadModule = [&ssErrorString](const sdv::u8string& rssModule) -> bool
|
||||
{
|
||||
bool bLocalRet = GetModuleControl().Load(rssModule);
|
||||
if (!bLocalRet) ssErrorString = std::string("Failed to load module '") + rssModule + "'.";
|
||||
return bLocalRet;
|
||||
};
|
||||
auto fnCreateObject = [&ssErrorString](const sdv::u8string& rssClass, const sdv::u8string& rssObject, const sdv::u8string& rssConfig) -> bool
|
||||
{
|
||||
bool bLocalRet = GetRepository().CreateObject2(rssClass, rssObject, rssConfig);
|
||||
if (!bLocalRet)
|
||||
{
|
||||
ssErrorString = std::string("Failed to instantiate a new object from class '") + rssClass + "'";
|
||||
if (!rssObject.empty())
|
||||
ssErrorString += std::string(" with name '") + rssObject + "'.";
|
||||
}
|
||||
return bLocalRet;
|
||||
};
|
||||
|
||||
// Load the core services
|
||||
if (!fnLoadModule("core_services.sdv"))
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to load the Core Services. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the logger module if one is specified
|
||||
if (!m_pathLoggerModule.empty())
|
||||
{
|
||||
m_tLoggerModuleID = fnLoadModule(m_pathLoggerModule.u8string());
|
||||
if (!m_tLoggerModuleID)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to load the custom logger. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Start the logger and assign it to the logger control.
|
||||
fnCreateObject(m_ssLoggerClass, m_ssLoggerClass, "");
|
||||
sdv::IInterfaceAccess* pLoggerObj = GetRepository().GetObject(m_ssLoggerClass);
|
||||
if (!pLoggerObj)
|
||||
{
|
||||
GetRepository().DestroyObject2(m_ssLoggerClass);
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
sdv::core::ILoggerConfig* pLoggerConfig = pLoggerObj->GetInterface<sdv::core::ILoggerConfig>();
|
||||
sdv::core::ILogger* pLogger = pLoggerObj->GetInterface<sdv::core::ILogger>();
|
||||
if (!pLoggerConfig || !pLogger)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
if (!m_ssProgramTag.empty())
|
||||
pLoggerConfig->SetProgramTag(m_ssProgramTag);
|
||||
pLoggerConfig->SetLogFilter(m_eSeverityFilter, m_eSeverityViewFilter);
|
||||
GetLoggerControl().SetLogger(pLogger);
|
||||
|
||||
// Create the core service objects
|
||||
bRet = fnCreateObject("AppControlService", "AppControlService", "");
|
||||
bRet = bRet && fnCreateObject("RepositoryService", "RepositoryService", "");
|
||||
bRet = bRet && fnCreateObject("ModuleControlService", "ModuleControlService", "");
|
||||
bRet = bRet && fnCreateObject("ConfigService", "ConfigService", "");
|
||||
if (!bRet)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
{
|
||||
std::cerr << "ERROR: Failed to start the Core Services. Cannot continue!" << std::endl;
|
||||
if (!ssErrorString.empty())
|
||||
std::cerr << "REASON: " << ssErrorString << std::endl;
|
||||
}
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load specific services
|
||||
bool bLoadRPCClient = false, bLoadRPCServer = false;
|
||||
bool bLoadInstallationManifests = false;
|
||||
switch (m_eContextMode)
|
||||
{
|
||||
case sdv::app::EAppContext::standalone:
|
||||
break;
|
||||
case sdv::app::EAppContext::external:
|
||||
bLoadRPCClient = true;
|
||||
break;
|
||||
case sdv::app::EAppContext::isolated:
|
||||
bLoadInstallationManifests = true;
|
||||
bLoadRPCClient = true;
|
||||
break;
|
||||
case sdv::app::EAppContext::main:
|
||||
bLoadInstallationManifests = true;
|
||||
bLoadRPCClient = true;
|
||||
bLoadRPCServer = true;
|
||||
break;
|
||||
case sdv::app::EAppContext::essential:
|
||||
//bLoadRPCClient = true;
|
||||
break;
|
||||
case sdv::app::EAppContext::maintenance:
|
||||
bLoadRPCClient = true;
|
||||
break;
|
||||
default:
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Invalid Run-As mode. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load installation manifests for main and isolated applications.
|
||||
if (bLoadInstallationManifests)
|
||||
{
|
||||
if (!GetAppConfig().LoadInstallationManifests())
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to load installation manifests. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load process control
|
||||
if (bRet) bRet = fnLoadModule("process_control.sdv") ? true : false;
|
||||
if (bRet) bRet = fnCreateObject("ProcessControlService", "", "");
|
||||
|
||||
// Load RPC components
|
||||
if (bRet && bLoadRPCServer)
|
||||
{
|
||||
// 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 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()));
|
||||
|
||||
if (!bRet)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to load IPC server components. Cannot continue!" << std::endl;
|
||||
if (!ssErrorString.empty())
|
||||
std::cerr << "REASON: " << ssErrorString << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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 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", "", "");
|
||||
|
||||
// Load proxy/stub for core interfaces
|
||||
if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false;
|
||||
|
||||
if (!bRet)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to load IPC client components. Cannot continue!" << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bRet)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to start core components. Cannot continue!" << std::endl;
|
||||
if (!ssErrorString.empty())
|
||||
std::cerr << "REASON: " << ssErrorString << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register the exit handler to force a proper shutdown in case the application exits without a call to shutdown.
|
||||
std::atexit(ExitHandler);
|
||||
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::initialized);
|
||||
|
||||
SetConfigMode();
|
||||
|
||||
if (IsMainApplication())
|
||||
{
|
||||
// Load system configs - they need to be present completely
|
||||
for (const std::filesystem::path& rpathConfig : m_vecSysConfigs)
|
||||
{
|
||||
sdv::core::EConfigProcessResult eResult = GetAppConfig().LoadConfig(rpathConfig.generic_u8string());
|
||||
if (eResult != sdv::core::EConfigProcessResult::successful)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Cannot load or partly load the system configuration: " <<
|
||||
rpathConfig.generic_u8string() << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The system configs should not be stored once more.
|
||||
GetAppConfig().ResetConfigBaseline();
|
||||
|
||||
// Load the application config - they can also be partly there
|
||||
if (!m_pathAppConfig.empty())
|
||||
{
|
||||
sdv::core::EConfigProcessResult eResult = GetAppConfig().LoadConfig(m_pathAppConfig.generic_u8string());
|
||||
if (eResult == sdv::core::EConfigProcessResult::failed)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Cannot load application configuration: " << m_pathAppConfig.generic_u8string() << std::endl;
|
||||
Shutdown(true);
|
||||
return false;
|
||||
}
|
||||
else if (eResult != sdv::core::EConfigProcessResult::partially_successful)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "WARNING: Partially could not load application configuration: " <<
|
||||
m_pathAppConfig.generic_u8string() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetRunningMode();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAppControl::RunLoop()
|
||||
{
|
||||
// Only allowed to run during configuration and running mode.
|
||||
switch (m_eState)
|
||||
{
|
||||
case sdv::app::EAppOperationState::configuring :
|
||||
case sdv::app::EAppOperationState::running :
|
||||
break;
|
||||
default:
|
||||
throw sdv::XInvalidState();
|
||||
}
|
||||
|
||||
// Treat local running differently from main and isolated app running.
|
||||
// Note... Maintenance apps should not use the loop
|
||||
bool bLocal = true;
|
||||
switch (m_eContextMode)
|
||||
{
|
||||
case sdv::app::EAppContext::main:
|
||||
case sdv::app::EAppContext::isolated:
|
||||
bLocal = false;
|
||||
break;
|
||||
case sdv::app::EAppContext::maintenance:
|
||||
throw sdv::XAccessDenied();
|
||||
break;
|
||||
default:
|
||||
bLocal = true;
|
||||
break;
|
||||
}
|
||||
|
||||
CShutdownRequestListener listener;
|
||||
if (bLocal)
|
||||
{
|
||||
listener = std::move(CShutdownRequestListener(m_uiInstanceID));
|
||||
if (!listener.IsValid())
|
||||
throw sdv::XAccessDenied(); // Another instance is already running.
|
||||
}
|
||||
|
||||
if (m_bVerbose)
|
||||
std::cout << "Entering loop" << std::endl;
|
||||
|
||||
m_bRunLoop = true;
|
||||
while (m_bRunLoop)
|
||||
{
|
||||
// Check and wait 2 milliseconds.
|
||||
if (bLocal)
|
||||
m_bRunLoop = !listener.HasTriggered(2);
|
||||
else
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
|
||||
// Inform about running.
|
||||
if (m_pEvent)
|
||||
{
|
||||
sdv::app::SAppEvent sEvent{};
|
||||
sEvent.uiEventID = sdv::app::EVENT_RUNNING_LOOP;
|
||||
sEvent.uiInfo = 0u;
|
||||
m_pEvent->ProcessEvent(sEvent);
|
||||
}
|
||||
}
|
||||
if (m_bVerbose)
|
||||
std::cout << "Leaving loop" << std::endl;
|
||||
}
|
||||
|
||||
void CAppControl::Shutdown(/*in*/ bool bForce)
|
||||
{
|
||||
if (m_eState == sdv::app::EAppOperationState::not_started) return;
|
||||
|
||||
// Shutdown the running loop
|
||||
if (m_bRunLoop)
|
||||
{
|
||||
m_bRunLoop = false;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::shutting_down);
|
||||
|
||||
// Set the config state for all objects
|
||||
GetRepository().SetConfigMode();
|
||||
|
||||
// Disable automatic configuration saving.
|
||||
m_bAutoSaveConfig = false;
|
||||
|
||||
// Destroy all objects... this should also remove any registered services, except the custom logger.
|
||||
GetRepository().DestroyAllObjects(std::vector<std::string>({ m_ssLoggerClass }), bForce);
|
||||
|
||||
// Unload all modules... this should destroy all running services, except the custom logger
|
||||
GetModuleControl().UnloadAll(std::vector<sdv::core::TModuleID>({ m_tLoggerModuleID }));
|
||||
|
||||
// Remove all installation manifests
|
||||
GetAppConfig().UnloadInstallatonManifests();
|
||||
|
||||
// End logging
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||||
if (bForce) SDV_LOG_INFO("Forced shutdown of application.");
|
||||
SDV_LOG_INFO("SDV Application end at ", std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S %Z"));
|
||||
GetLoggerControl().SetLogger(nullptr);
|
||||
|
||||
// Unload all modules... this should also destroy the custom logger.
|
||||
GetModuleControl().UnloadAll(std::vector<sdv::core::TModuleID>());
|
||||
|
||||
// Release the lock on the instance (only for main application)
|
||||
if (m_pLockFile)
|
||||
{
|
||||
#ifdef __unix__
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-result"
|
||||
#endif
|
||||
lockf(fileno(m_pLockFile), F_ULOCK, 0);
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#endif
|
||||
fclose(m_pLockFile);
|
||||
m_pLockFile = nullptr;
|
||||
try
|
||||
{
|
||||
if (std::filesystem::exists(m_pathLockFile))
|
||||
std::filesystem::remove(m_pathLockFile);
|
||||
} catch (const std::filesystem::filesystem_error&)
|
||||
{}
|
||||
m_pathLockFile.clear();
|
||||
}
|
||||
|
||||
// End trace streaming
|
||||
if (m_eContextMode == sdv::app::EAppContext::main)
|
||||
{
|
||||
std::cout << "**********************************************" << std::endl;
|
||||
|
||||
// Shutdown log streaming
|
||||
m_fifoTraceStreamBuffer.Close();
|
||||
}
|
||||
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::not_started);
|
||||
m_eContextMode = sdv::app::EAppContext::no_context;
|
||||
m_uiInstanceID = 0u;
|
||||
m_pEvent = nullptr;
|
||||
m_ssLoggerClass.clear();
|
||||
m_tLoggerModuleID = 0;
|
||||
m_pathLoggerModule.clear();
|
||||
m_ssProgramTag.clear();
|
||||
m_eSeverityFilter = sdv::core::ELogSeverity::info;
|
||||
m_pathInstallDir.clear();
|
||||
m_pathRootDir.clear();
|
||||
m_vecSysConfigs.clear();
|
||||
m_pathAppConfig.clear();
|
||||
m_bAutoSaveConfig = false;
|
||||
m_bEnableAutoSave = false;
|
||||
m_bSilent = false;
|
||||
m_bVerbose = false;
|
||||
}
|
||||
|
||||
void CAppControl::RequestShutdown()
|
||||
{
|
||||
SDV_LOG_INFO("Shutdown request received.");
|
||||
m_bRunLoop = false;
|
||||
}
|
||||
|
||||
sdv::app::EAppOperationState CAppControl::GetOperationState() const
|
||||
{
|
||||
return m_eState;
|
||||
}
|
||||
|
||||
uint32_t CAppControl::GetInstance() const
|
||||
{
|
||||
return m_uiInstanceID;
|
||||
}
|
||||
|
||||
void CAppControl::SetConfigMode()
|
||||
{
|
||||
GetRepository().SetConfigMode();
|
||||
if (m_eState == sdv::app::EAppOperationState::running || m_eState == sdv::app::EAppOperationState::initialized)
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::configuring);
|
||||
}
|
||||
|
||||
void CAppControl::SetRunningMode()
|
||||
{
|
||||
GetRepository().SetRunningMode();
|
||||
if (m_eState == sdv::app::EAppOperationState::configuring || m_eState == sdv::app::EAppOperationState::initialized)
|
||||
BroadcastOperationState(sdv::app::EAppOperationState::running);
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::u8string> CAppControl::GetNames() const
|
||||
{
|
||||
sdv::sequence<sdv::u8string> seqNames = {"app.instance_id", "console.info_level"};
|
||||
return seqNames;
|
||||
}
|
||||
|
||||
sdv::any_t CAppControl::Get(/*in*/ const sdv::u8string& ssAttribute) const
|
||||
{
|
||||
if (ssAttribute == "app.instance_id") return sdv::any_t(m_uiInstanceID);
|
||||
if (ssAttribute == "console.info_level")
|
||||
{
|
||||
if (m_bSilent) return "silent";
|
||||
if (m_bVerbose) return "verbose";
|
||||
return "normal";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool CAppControl::Set(/*in*/ const sdv::u8string& /*ssAttribute*/, /*in*/ sdv::any_t /*anyAttribute*/)
|
||||
{
|
||||
// Currently there are not setting attributes...
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t CAppControl::GetFlags(/*in*/ const sdv::u8string& ssAttribute) const
|
||||
{
|
||||
if (ssAttribute == "app.instance_id") return hlpr::flags<sdv::EAttributeFlags>(sdv::EAttributeFlags::read_only);
|
||||
if (ssAttribute == "console.info_level") return hlpr::flags<sdv::EAttributeFlags>(sdv::EAttributeFlags::read_only);
|
||||
return 0u;
|
||||
}
|
||||
|
||||
std::filesystem::path CAppControl::GetInstallDir() const
|
||||
{
|
||||
return m_pathInstallDir;
|
||||
}
|
||||
|
||||
void CAppControl::DisableAutoConfigUpdate()
|
||||
{
|
||||
m_bEnableAutoSave = false;
|
||||
}
|
||||
|
||||
void CAppControl::EnableAutoConfigUpdate()
|
||||
{
|
||||
m_bEnableAutoSave = true;
|
||||
}
|
||||
|
||||
void CAppControl::TriggerConfigUpdate()
|
||||
{
|
||||
if (!m_bAutoSaveConfig || !m_bEnableAutoSave) return;
|
||||
if (m_pathAppConfig.empty()) return;
|
||||
|
||||
if (!GetAppConfig().SaveConfig(m_pathAppConfig.generic_u8string()))
|
||||
SDV_LOG_ERROR("Failed to automatically save the configuration ", m_pathAppConfig.generic_u8string());
|
||||
}
|
||||
|
||||
bool CAppControl::IsConsoleSilent() const
|
||||
{
|
||||
return m_bSilent;
|
||||
}
|
||||
|
||||
bool CAppControl::IsConsoleVerbose() const
|
||||
{
|
||||
return m_bVerbose;
|
||||
}
|
||||
|
||||
void CAppControl::BroadcastOperationState(sdv::app::EAppOperationState eState)
|
||||
{
|
||||
m_eState = eState;
|
||||
if (m_pEvent)
|
||||
{
|
||||
sdv::app::SAppEvent sEvent{};
|
||||
sEvent.uiEventID = sdv::app::EVENT_OPERATION_STATE_CHANGED;
|
||||
sEvent.uiInfo = static_cast<uint32_t>(eState);
|
||||
m_pEvent->ProcessEvent(sEvent);
|
||||
}
|
||||
}
|
||||
|
||||
bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig)
|
||||
{
|
||||
CParserTOML parserConfig;
|
||||
std::string ssError;
|
||||
try
|
||||
{
|
||||
// Read the configuration
|
||||
if (!parserConfig.Process(rssConfig)) return false;
|
||||
|
||||
} catch (const sdv::toml::XTOMLParseException& rexcept)
|
||||
{
|
||||
ssError = std::string("ERROR: Failed to parse application configuration: ") + rexcept.what();
|
||||
}
|
||||
|
||||
// Get the reporting settings (if this succeeded at all...)
|
||||
auto ptrReport = parserConfig.GetRoot().GetDirect("Console.Report");
|
||||
if (ptrReport && ptrReport->GetValue() == "Silent") m_bSilent = true;
|
||||
if (ptrReport && ptrReport->GetValue() == "Verbose") m_bVerbose = true;
|
||||
|
||||
// Report the outstanding error (if there is one...)
|
||||
if (!ssError.empty())
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << ssError << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow a custom logger to be defined
|
||||
std::filesystem::path pathLoggerModule;
|
||||
std::string ssLoggerClass;
|
||||
std::shared_ptr<CNode> ptrLogHandlerPath = parserConfig.GetRoot().GetDirect("LogHandler.Path");
|
||||
std::shared_ptr<CNode> ptrLogHandlerClass = parserConfig.GetRoot().GetDirect("LogHandler.Class");
|
||||
if (ptrLogHandlerPath && !ptrLogHandlerClass)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to process application log: custom logger handler module path supplied, but no class "
|
||||
"defined!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (!ptrLogHandlerPath && ptrLogHandlerClass)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to process application log: custom logger handler class supplied, but no module "
|
||||
"defined!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (ptrLogHandlerPath)
|
||||
{
|
||||
m_pathLoggerModule = static_cast<std::string>(ptrLogHandlerPath->GetValue());
|
||||
m_ssLoggerClass = static_cast<std::string>(ptrLogHandlerClass->GetValue());
|
||||
} else
|
||||
{
|
||||
// Default logger
|
||||
m_ssLoggerClass = "DefaultLoggerService";
|
||||
}
|
||||
|
||||
// Get an optional program tag for the logger
|
||||
std::shared_ptr<CNode> ptrLogPogramTag = parserConfig.GetRoot().GetDirect("LogHandler.Tag");
|
||||
if (ptrLogPogramTag) m_ssProgramTag = static_cast<std::string>(ptrLogPogramTag->GetValue());
|
||||
|
||||
// Get the application-mode
|
||||
std::string ssApplication = "Standalone";
|
||||
std::shared_ptr<CNode> ptrApplication = parserConfig.GetRoot().GetDirect("Application.Mode");
|
||||
if (ptrApplication) ssApplication = static_cast<std::string>(ptrApplication->GetValue());
|
||||
if (ssApplication == "Standalone") m_eContextMode = sdv::app::EAppContext::standalone;
|
||||
else if (ssApplication == "External") m_eContextMode = sdv::app::EAppContext::external;
|
||||
else if (ssApplication == "Isolated") m_eContextMode = sdv::app::EAppContext::isolated;
|
||||
else if (ssApplication == "Main") m_eContextMode = sdv::app::EAppContext::main;
|
||||
else if (ssApplication == "Essential") m_eContextMode = sdv::app::EAppContext::essential;
|
||||
else if (ssApplication == "Maintenance") m_eContextMode = sdv::app::EAppContext::maintenance;
|
||||
else
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to process startup config: invalid application-mode specified for core library: " <<
|
||||
ssApplication << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the severity level filter for the logger
|
||||
auto fnTranslateSevFilter = [this](const std::string& rssLogFilter, const sdv::core::ELogSeverity eDefault)
|
||||
{
|
||||
sdv::core::ELogSeverity eSeverityFilter = eDefault;
|
||||
if (rssLogFilter == "Trace") eSeverityFilter = sdv::core::ELogSeverity::trace;
|
||||
else if (rssLogFilter == "Debug") eSeverityFilter = sdv::core::ELogSeverity::debug;
|
||||
else if (rssLogFilter == "Info") eSeverityFilter = sdv::core::ELogSeverity::info;
|
||||
else if (rssLogFilter == "Warning") eSeverityFilter = sdv::core::ELogSeverity::warning;
|
||||
else if (rssLogFilter == "Error") eSeverityFilter = sdv::core::ELogSeverity::error;
|
||||
else if (rssLogFilter == "Fatal") eSeverityFilter = sdv::core::ELogSeverity::fatal;
|
||||
else if (!rssLogFilter.empty())
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to process application log: invalid severity level filter: '" << rssLogFilter <<
|
||||
"'" << std::endl;
|
||||
}
|
||||
return eSeverityFilter;
|
||||
};
|
||||
sdv::core::ELogSeverity eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::error;
|
||||
if (IsMainApplication() || IsIsolatedApplication())
|
||||
eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::info;
|
||||
std::shared_ptr<CNode> ptrLogSeverityFilter = parserConfig.GetRoot().GetDirect("LogHandler.Filter");
|
||||
m_eSeverityFilter = fnTranslateSevFilter(ptrLogSeverityFilter ? ptrLogSeverityFilter->GetValue() : "",
|
||||
sdv::core::ELogSeverity::info);
|
||||
ptrLogSeverityFilter = parserConfig.GetRoot().GetDirect("LogHandler.ViewFilter");
|
||||
m_eSeverityViewFilter = fnTranslateSevFilter(ptrLogSeverityFilter ? ptrLogSeverityFilter->GetValue() : "",
|
||||
eLogDefaultViewSeverityFilter);
|
||||
|
||||
// Get the optional instance ID.
|
||||
std::shared_ptr<CNode> ptrInstance = parserConfig.GetRoot().GetDirect("Application.Instance");
|
||||
if (ptrInstance)
|
||||
m_uiInstanceID = ptrInstance->GetValue();
|
||||
else
|
||||
m_uiInstanceID = 1000u;
|
||||
// Number of attempts to establish a connection to a running instance.
|
||||
std::shared_ptr<CNode> ptrRetries = parserConfig.GetRoot().GetDirect("Application.Retries");
|
||||
if (ptrRetries)
|
||||
{
|
||||
m_uiRetries = ptrRetries->GetValue();
|
||||
if (m_uiRetries > 30)
|
||||
m_uiRetries = 30;
|
||||
else if (m_uiRetries < 3)
|
||||
m_uiRetries = 3;
|
||||
}
|
||||
|
||||
// Main and isolated apps specific information.
|
||||
if (IsMainApplication() || IsIsolatedApplication())
|
||||
{
|
||||
// Get the optional installation directory.
|
||||
std::shared_ptr<CNode> ptrInstallDir = parserConfig.GetRoot().GetDirect("Application.InstallDir");
|
||||
if (ptrInstallDir)
|
||||
{
|
||||
m_pathRootDir = ptrInstallDir->GetValue();
|
||||
if (m_pathRootDir.is_relative())
|
||||
m_pathRootDir = GetExecDirectory() / m_pathRootDir;
|
||||
}
|
||||
else
|
||||
m_pathRootDir = GetExecDirectory() / std::to_string(m_uiInstanceID);
|
||||
m_pathInstallDir = m_pathRootDir;
|
||||
try
|
||||
{
|
||||
std::filesystem::create_directories(m_pathRootDir);
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& rexcept)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
{
|
||||
std::cerr << "Cannot create root directory: " << m_pathRootDir << std::endl;
|
||||
std::cerr << " Reason: " << rexcept.what() << std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Maintenance and isolated applications cannot load specific configs. The others can specify a configuration file, but
|
||||
// not auto-updateable.
|
||||
if (!IsMaintenanceApplication() && !IsIsolatedApplication())
|
||||
{
|
||||
auto ptrConfigFile = parserConfig.GetRoot().GetDirect("Application.Config");
|
||||
if (ptrConfigFile)
|
||||
m_pathAppConfig = ptrConfigFile->GetValue();
|
||||
}
|
||||
|
||||
// Read the settings... if existing. And only for the main application
|
||||
if (IsMainApplication())
|
||||
{
|
||||
// If the template is not existing, create the template...
|
||||
if (!std::filesystem::exists(m_pathRootDir / "settings.toml"))
|
||||
{
|
||||
std::ofstream fstream(m_pathRootDir / "settings.toml");
|
||||
if (!fstream.is_open())
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to store application settings." << std::endl;
|
||||
return false;
|
||||
}
|
||||
fstream << szSettingsTemplate;
|
||||
fstream.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ifstream fstream(m_pathRootDir / "settings.toml");
|
||||
std::string ssContent((std::istreambuf_iterator<char>(fstream)), std::istreambuf_iterator<char>());
|
||||
try
|
||||
{
|
||||
// Read the configuration
|
||||
CParserTOML parserSettings(ssContent);
|
||||
|
||||
// Check for the version
|
||||
auto ptrVersion = parserSettings.GetRoot().GetDirect("Settings.Version");
|
||||
if (!ptrVersion)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Missing version in application settings file." << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (ptrVersion->GetValue() != SDVFrameworkInterfaceVersion)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Invalid version of application settings file (expected version " <<
|
||||
SDVFrameworkInterfaceVersion << ", but available version " <<
|
||||
static_cast<uint32_t>(ptrVersion->GetValue()) << ")" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the system configurations
|
||||
auto ptrSystemConfigs = parserSettings.GetRoot().GetDirect("Settings.SystemConfig");
|
||||
if (ptrSystemConfigs && ptrSystemConfigs->GetArray())
|
||||
{
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrSystemConfigs->GetArray()->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrSystemConfig = ptrSystemConfigs->GetArray()->Get(uiIndex);
|
||||
if (!ptrSystemConfig) continue;
|
||||
m_vecSysConfigs.push_back(static_cast<std::string>(ptrSystemConfig->GetValue()));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the application config - but only when not specified over the app-control-config.
|
||||
if (m_pathAppConfig.empty())
|
||||
{
|
||||
auto ptrAppConfig = parserSettings.GetRoot().GetDirect("Settings.AppConfig");
|
||||
if (ptrAppConfig)
|
||||
{
|
||||
// Path available. Enable auto-update.
|
||||
m_pathAppConfig = static_cast<std::string>(ptrAppConfig->GetValue());
|
||||
m_bAutoSaveConfig = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException& rexcept)
|
||||
{
|
||||
if (!m_bSilent)
|
||||
std::cerr << "ERROR: Failed to parse application settings: " << rexcept.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
CAppControl& CAppControlService::GetAppControl()
|
||||
{
|
||||
return ::GetAppControl();
|
||||
}
|
||||
|
||||
bool CAppControlService::EnableAppShutdownRequestAccess() const
|
||||
{
|
||||
return ::GetAppControl().IsMainApplication() || ::GetAppControl().IsIsolatedApplication();
|
||||
}
|
||||
|
||||
#endif
|
||||
344
sdv_services/core/app_control.h
Normal file
344
sdv_services/core/app_control.h
Normal file
@@ -0,0 +1,344 @@
|
||||
#ifndef APP_CONTROL_H
|
||||
#define APP_CONTROL_H
|
||||
|
||||
#include <interfaces/app.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <support/interface_ptr.h>
|
||||
#include "../../global/tracefifo/trace_fifo.h"
|
||||
|
||||
///
|
||||
/// @brief Application control class.
|
||||
/// @details The application control class is responsible for the startup and shutdown of the system. Since the system is (re-)used
|
||||
/// in many applications, the startup behavior can be determined by the provided configuration as string as argument to the
|
||||
/// startup function.
|
||||
/// The configuration uses the TOML format and is defined as follows:
|
||||
/// @code
|
||||
/// # Optional use of customized log handler
|
||||
/// [LogHandler]
|
||||
/// Class = "" # Component class name of a custom logger (optional)
|
||||
/// Path = "" # Component module path of a custom logger (optional)
|
||||
/// Tag = "" # Program tag to use instead of the name SDV_LOG_<pid>
|
||||
/// Filter = "" # Lowest severity filter to use when logging (Trace, Debug, Info, Warning, Error, Fatal). Default severity
|
||||
/// # level filter is Info (meaning Debug and Trace messages are not being stored).
|
||||
/// ViewFilter = "" # Lowest severity filter to use when logging (Trace, Debug, Info, Warning, Error, Fatal). Default severity
|
||||
/// # level filter is Error (meaning Debug, Trace, Info and Warning messages are not being shown).
|
||||
///
|
||||
/// # Application behavior definition
|
||||
/// # Mode = "Standalone" (default) app->no RPC + core services + additional configurations allowed
|
||||
/// # Mode = "External" app->RPC client only + local services + target service(s) --> connection information through listener
|
||||
/// # Mode = "Isolated" app->RPC client only + local services + target service(s) --> connection information needed
|
||||
/// # Mode = "Main" app->RPC server + core services --> access key needed
|
||||
/// # Mode = "Essential" app->local services + additional configurations allowed
|
||||
/// # Mode = "Maintenance" app->RPC client only + local services + maintenance service --> connection information needed + access key
|
||||
/// # Instance = 1234
|
||||
/// [Application]
|
||||
/// Mode = "Main"
|
||||
/// Instance = 1234 # Optional instance ID to be used with main and isolated applications. Has no influence on other
|
||||
/// # applications. Default instance ID is 1000. The connection listener is using the instance ID to allow
|
||||
/// # connections from an external application to the main application. Furthermore, the instance ID is
|
||||
/// # used to locate the installation of SDV components. The location of the SDV components is relative to
|
||||
/// # the executable (unless a target directory is supplied) added with the instance and the installations:
|
||||
/// # <exe_path>/<instance>/<installation>
|
||||
/// InstallDir = "./test" # Optional custom installation directory to be used with main and isolated applications. Has no
|
||||
/// # influence on other applications. The default location for installations is the location of the
|
||||
/// # executable. Specifying a different directory will change the location of installations to
|
||||
/// # <install_directory>/<instance>/<installation>
|
||||
/// # NOTE The directory of the core library and the directory of the running executable are always added
|
||||
/// # to the system if they contain an installation manifest.
|
||||
///
|
||||
/// # Optional configuration that should be loaded (not for maintenance and isolated applications). This overrides the application
|
||||
/// # config from the settings (only main application). Automatic saving the configuration is not supported.
|
||||
/// Config = "abc.toml"
|
||||
///
|
||||
/// #Console output
|
||||
/// [Console]
|
||||
/// Report = "Silent" # Either "Silent", "Normal" or "Verbose" for no, normal or extensive messages.
|
||||
///
|
||||
/// # Search directories
|
||||
/// @endcode
|
||||
///
|
||||
/// TODO: Add config ignore list (e.g. platform.toml, vehicle_ifc.toml and vehicle_abstract.toml).
|
||||
/// Add dedicated config (rather than standard config) as startup param.
|
||||
///
|
||||
class CAppControl : public sdv::IInterfaceAccess, public sdv::app::IAppContext, public sdv::app::IAppControl,
|
||||
public sdv::app::IAppOperation, public sdv::app::IAppShutdownRequest, public sdv::IAttributes
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CAppControl();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CAppControl();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::app::IAppOperation)
|
||||
SDV_INTERFACE_ENTRY(sdv::app::IAppContext)
|
||||
SDV_INTERFACE_ENTRY(sdv::app::IAppControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::app::IAppShutdownRequest)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is the main application.
|
||||
* @return Returns 'true' when the current application is the main application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsMainApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is an isolated application.
|
||||
* @return Returns 'true' when the current application is an isolated application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsIsolatedApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is a standalone application.
|
||||
* @return Returns 'true' when the current application is a standalone application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsStandaloneApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is an essential application.
|
||||
* @return Returns 'true' when the current application is an essential application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsEssentialApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is a maintenance application.
|
||||
* @return Returns 'true' when the current application is a maintenance application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsMaintenanceApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return whether the current application is an external application.
|
||||
* @return Returns 'true' when the current application is an external application; otherwise returns 'false'.
|
||||
*/
|
||||
bool IsExternalApplication() const;
|
||||
|
||||
/**
|
||||
* @brief Return the application context mode. Overload of sdv::app::IAppContext::GetContextType.
|
||||
* @return The context mode.
|
||||
*/
|
||||
sdv::app::EAppContext GetContextType() const override;
|
||||
|
||||
/**
|
||||
* @brief Return the core instance ID. Overload of sdv::app::IAppContext::GetContextType.
|
||||
* @return The instance ID.
|
||||
*/
|
||||
uint32_t GetInstanceID() const override;
|
||||
|
||||
/**
|
||||
* @brief Return the number of retries to establish a connection.
|
||||
* @return Number of retries.
|
||||
*/
|
||||
uint32_t GetRetries() const override;
|
||||
|
||||
/**
|
||||
* @brief Start the application. Overload of sdv::app::IAppControl::Startup.
|
||||
* @details The core will prepare for an application start based on the provided configuration. Per default, the
|
||||
* application will be in running mode after successful startup.
|
||||
* @param[in] ssConfig String containing the configuration to be used by the core during startup. This configuration
|
||||
* is optional. If not provided the application runs as standalone application without any RPC support.
|
||||
* @param[in] pEventHandler Pointer to the event handler receiving application events. For the handler to receive
|
||||
* events, the handler needs to expose the IAppEvent interface. This pointer is optionally and can be NULL.
|
||||
* @return Returns 'true' on successful start-up and 'false' on failed startup.
|
||||
*/
|
||||
virtual bool Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfaceAccess* pEventHandler) override;
|
||||
|
||||
/**
|
||||
* @brief Running loop until shutdown request is triggered.
|
||||
*/
|
||||
virtual void RunLoop() override;
|
||||
|
||||
/**
|
||||
* @brief Initiate a shutdown of the application. Overload of sdv::app::IAppControl::Shutdown.
|
||||
* @details The objects will be called to shutdown allowing them to clean up and gracefully shutdown. If, for some reason, the
|
||||
* object cannot shut down (e.g. pointers are still in use or threads are not finalized), the object will be kept alive and the
|
||||
* application state will stay in shutting-down-state. In that case the exception is called. A new call to the shutdown function
|
||||
* using the force-flag might force a shutdown. Alternatively the application can wait until the application state changes to
|
||||
* not-started.
|
||||
* @remarks Application shutdown is only possible when all components are released.
|
||||
* @param[in] bForce When set, forces an application shutdown. This might result in loss of data and should only be used as a
|
||||
* last resort.
|
||||
*/
|
||||
virtual void Shutdown(/*in*/ bool bForce) override;
|
||||
|
||||
/**
|
||||
* @brief Request shutdown. Overload of sdv::app::IAppShutdownRequest::RequestShutdown.
|
||||
*/
|
||||
virtual void RequestShutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Get the current operation state. This information is also supplied through the event handler function. Overload of
|
||||
* sdv::app::IAppOperation::GetOperationState.
|
||||
* @return Returns the operation state of the application.
|
||||
*/
|
||||
virtual sdv::app::EAppOperationState GetOperationState() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the current running instance.
|
||||
* @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. A dedicated
|
||||
* instance can be supplied through the app control.
|
||||
* @return The instance number.
|
||||
*/
|
||||
uint32_t GetInstance() const;
|
||||
|
||||
/**
|
||||
* @brief Switch from running mode to the configuration mode. Overload of sdv::app::IAppOperation::SetConfigMode.
|
||||
*/
|
||||
virtual void SetConfigMode() override;
|
||||
|
||||
/**
|
||||
* @brief Switch from the configuration mode to the running mode. Overload of sdv::app::IAppOperation::SetRunningMode.
|
||||
*/
|
||||
virtual void SetRunningMode() override;
|
||||
|
||||
/**
|
||||
* @brief Get a sequence with the available attribute names. Overload of sdv::IAttributes::GetNames.
|
||||
* @return The sequence of attribute names.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::u8string> GetNames() const override;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
virtual sdv::any_t Get(/*in*/ const sdv::u8string& ssAttribute) 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.
|
||||
*/
|
||||
virtual bool Set(/*in*/ const sdv::u8string& ssAttribute, /*in*/ sdv::any_t anyAttribute) 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.
|
||||
*/
|
||||
virtual uint32_t GetFlags(/*in*/ const sdv::u8string& ssAttribute) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the installation directory of user components.
|
||||
* @return The location of the user components. Only is valid when used in main and isolated applications.
|
||||
*/
|
||||
std::filesystem::path GetInstallDir() const;
|
||||
|
||||
/**
|
||||
* @brief Disable the current auto update feature if enabled in the system settings.
|
||||
*/
|
||||
void DisableAutoConfigUpdate();
|
||||
|
||||
/**
|
||||
* @brief Enable the current auto update feature if enabled in the system settings.
|
||||
*/
|
||||
void EnableAutoConfigUpdate();
|
||||
|
||||
/**
|
||||
* @brief Trigger the config update if enabled in the system settings.
|
||||
*/
|
||||
void TriggerConfigUpdate();
|
||||
|
||||
/**
|
||||
* @brief Should the console output be silent?
|
||||
* @return Returns whether the console output is silent.
|
||||
*/
|
||||
bool IsConsoleSilent() const;
|
||||
|
||||
/**
|
||||
* @brief Should the console output be verbose?
|
||||
* @return Returns whether the verbose console output is activated.
|
||||
*/
|
||||
bool IsConsoleVerbose() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Set the operation state and broadcast the state through the event.
|
||||
* @param[in] eState The new state the app control is in.
|
||||
*/
|
||||
void BroadcastOperationState(sdv::app::EAppOperationState eState);
|
||||
|
||||
/**
|
||||
* @brief Process the application configuration before starting the system
|
||||
* @param[in] rssConfig Reference to the configuration content.
|
||||
* @return Returns 'true' when processing was successful; false when not.
|
||||
*/
|
||||
bool ProcessAppConfig(const sdv::u8string& rssConfig);
|
||||
|
||||
sdv::app::EAppContext m_eContextMode = sdv::app::EAppContext::no_context; ///< The application is running as...
|
||||
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.
|
||||
std::string m_ssLoggerClass; ///< Class name of a logger service.
|
||||
sdv::core::TModuleID m_tLoggerModuleID = 0; ///< ID of the logger module.
|
||||
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.
|
||||
uint32_t m_uiRetries = 0u; ///< Number of retries to establish a connection.
|
||||
uint32_t m_uiInstanceID = 0u; ///< Instance number.
|
||||
std::filesystem::path m_pathInstallDir; ///< Location of user component installations.
|
||||
std::filesystem::path m_pathRootDir; ///< Location of user component root directory.
|
||||
std::vector<std::filesystem::path> m_vecSysConfigs; ///< The system configurations from the settings file.
|
||||
std::filesystem::path m_pathAppConfig; ///< The application configuration from the settings file.
|
||||
bool m_bAutoSaveConfig = false; ///< System setting for automatic saving of the configuration.
|
||||
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.
|
||||
bool m_bSilent = false; ///< When set, no console reporting takes place.
|
||||
bool m_bVerbose = false; ///< When set, extensive console reporting takes place.
|
||||
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.
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
/**
|
||||
* @brief App config service class.
|
||||
*/
|
||||
class CAppControlService : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
CAppControlService() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::app::IAppOperation, GetAppControl())
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::IAttributes, GetAppControl())
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableAppShutdownRequestAccess(), 1)
|
||||
SDV_INTERFACE_SECTION(1)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::app::IAppShutdownRequest, GetAppControl())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("AppControlService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Get access to the application control.
|
||||
* @return Returns the one global instance of the application config.
|
||||
*/
|
||||
static CAppControl& GetAppControl();
|
||||
|
||||
/**
|
||||
* @brief When set, the application shutdown request interface access will be enabled.
|
||||
* @return Returns 'true' when the access to the application configuration is granted; otherwise returns 'false'.
|
||||
*/
|
||||
bool EnableAppShutdownRequestAccess() const;
|
||||
};
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CAppControlService)
|
||||
|
||||
#endif
|
||||
|
||||
#endif // !defined APP_CONTROL_H
|
||||
1650
sdv_services/core/installation_composer.cpp
Normal file
1650
sdv_services/core/installation_composer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
470
sdv_services/core/installation_composer.h
Normal file
470
sdv_services/core/installation_composer.h
Normal file
@@ -0,0 +1,470 @@
|
||||
#ifndef INSTALL_PACKAGING_H
|
||||
#define INSTALL_PACKAGING_H
|
||||
|
||||
#include <interfaces/config.h>
|
||||
#include "installation_manifest.h"
|
||||
#include <list>
|
||||
#include <filesystem>
|
||||
|
||||
/// When enabled, support the read-only flag for files. By default this flag is not enabled due to limited support by the OS.
|
||||
/// - The Windows OS provides native support for this flag
|
||||
/// - The Linux allows installing additional libraries providing support, but this is not supported by all Linux versions.
|
||||
/// - The Posix API doesn't provide support for the read-only flag.
|
||||
#define COMPOSER_SUPPORT_READONLY_LINUX 0
|
||||
|
||||
#ifdef _WIN32
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
#pragma push_macro("GetObject")
|
||||
#undef GetObject
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("GetObject")
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Convert the Windows FILETIME to the POSIX file time in micro-seconds.
|
||||
* @details The Windows FILETIME structure counts with 100ns resolution and starts on the 1st of January 1601. The POSIX time
|
||||
* counts with 1 second resolution and starts on the 1st of January 1970. V-API uses 1 micro-second resolution.
|
||||
* @param[in] sWinTime The WIndows file time.
|
||||
* @return The POSIX time in micro-seconds.
|
||||
*/
|
||||
inline uint64_t WindowsTimeToPosixTime(FILETIME sWinTime)
|
||||
{
|
||||
const uint64_t uiTicksPerMicroSecond = 10ull; // Windows counts in 100ns; POSIX in seconds
|
||||
const uint64_t uiEpochDifference = 11644473600000000ull; // Windows starts counting on 1st Jan. 1601; POSIX on 1st Jan. 1970
|
||||
return ((static_cast<uint64_t>(sWinTime.dwHighDateTime) << 32 | static_cast<uint64_t>(sWinTime.dwLowDateTime)) /
|
||||
uiTicksPerMicroSecond) - uiEpochDifference;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert the POSIX file time in micro-seconds to the Windows FILETIME.
|
||||
* @details The Windows FILETIME structure counts with 100ns resolution and starts on the 1st of January 1601. The POSIX time
|
||||
* counts with 1 second resolution and starts on the 1st of January 1970. V-API uses 1 micro-second resolution.
|
||||
* @param[in] uiTime The POSIX time in micro-seconds.
|
||||
* @return The Windows FILETIME.
|
||||
*/
|
||||
inline FILETIME PosixTimeToWindowsTime(uint64_t uiTime)
|
||||
{
|
||||
FILETIME sWinTime{};
|
||||
const uint64_t uiTicksPerMicroSecond = 10ull; // Windows counts in 100ns; POSIX in seconds
|
||||
const uint64_t uiEpochDifference = 11644473600000000ull; // Windows starts counting on 1st Jan. 1601; POSIX on 1st Jan. 1970
|
||||
sWinTime.dwLowDateTime = static_cast<uint32_t>(((uiTime + uiEpochDifference) * uiTicksPerMicroSecond) & 0xffffffffull);
|
||||
sWinTime.dwHighDateTime = static_cast<uint32_t>(((uiTime + uiEpochDifference) * uiTicksPerMicroSecond) >> 32ull);
|
||||
return sWinTime;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Installation packager class
|
||||
*/
|
||||
class CInstallComposer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor.
|
||||
*/
|
||||
CInstallComposer() = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor, finalizing the installation if not done so before.
|
||||
*/
|
||||
~CInstallComposer();
|
||||
|
||||
/**
|
||||
* @brief Clear the current package to allow a new package composition.
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* @brief Flags to use with the AddModule function.
|
||||
*/
|
||||
enum class EAddModuleFlags : uint32_t
|
||||
{
|
||||
wildcards = 0x00, ///< Add all modules that fit the modules path using wildcards to search for modules; could also
|
||||
///< point to a directory. Wildcards are '*', '**' and '?'. The '*' will allow matching zero or
|
||||
///< more characters until the next separator ('.' or '/'). A '**' allows matching of zero or
|
||||
///< more characters across the directory separator '/'. A '?' allows matching one character.
|
||||
///< Examples (using path: /dir1/dir2/dir3/dir4/file.txt)
|
||||
///< - Pattern /dir1/**/*.txt - match
|
||||
///< - Pattern /dir?/dir?/**/dir4 - match
|
||||
///< - Pattern /**/*.txt - match
|
||||
regex = 0x01, ///< Add all modules that fit the modules path using regular expression to search for modules;
|
||||
///< could also point to a directory.
|
||||
keep_structure = 0x10, ///< Maintain the directory structure when adding modules (requires a valid base directory).
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Installation update rules.
|
||||
*/
|
||||
enum class EUpdateRules
|
||||
{
|
||||
not_allowed, ///< Not allowed to update an existing installation.
|
||||
update_when_new, ///< Allow to update when the installation is newer.
|
||||
overwrite, ///< Always allow to update.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Add a module to the installation.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
|
||||
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
|
||||
* @details Add a module to the installation package.
|
||||
* @param[in] rpathBasePath Reference to the base path to start searching for modules. Could be empty if the module path is
|
||||
* absolute, in which case the directory structure cannot be stored at the target.
|
||||
* @param[in] rssModulePath Reference to the string containing the module path to install. Depending on the options, this path
|
||||
* could contain a pattern with wildcards or regular expressions. If the path is relative, the module base path must be valid.
|
||||
* The module path is optional. If not supplied, the base path must be provided and all files within all subdirectories will
|
||||
* be added.
|
||||
* @param[in] rpathRelTargetDir Reference to the relative target directory the module(s) should be stored at.
|
||||
* @param[in] uiFlags Zero or more flags from EAddModuleFlags.
|
||||
* @return Returns the a vector of modules that were added. In case of using wildcards or regular expression, this could also
|
||||
* be an empty list, which is not an error.
|
||||
*/
|
||||
std::vector<std::filesystem::path> AddModule(const std::filesystem::path& rpathBasePath, const std::string& rssModulePath,
|
||||
const std::filesystem::path& rpathRelTargetDir = ".", uint32_t uiFlags = 0);
|
||||
|
||||
/**
|
||||
* @brief Add a property value. This value will be added to the installation manifest.
|
||||
* @details Add a property value, which will be included in the installation manifest. The properties "Description" and
|
||||
* "Version" are used during package management.
|
||||
* @param[in] rssName Name of the property. Spaces are allowed. Quotes are not allowed.
|
||||
* @param[in] rssValue Property value.
|
||||
*/
|
||||
void AddProperty(const std::string& rssName, const std::string& rssValue);
|
||||
|
||||
/**
|
||||
* @brief Compose the package in memory.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
|
||||
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
|
||||
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
|
||||
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
|
||||
* automatically extracted from the component and added to the installation manifest.
|
||||
* @param[in] rssInstallName Reference to the string containing the installation name.
|
||||
* @return Returns a buffer to the package content.
|
||||
*/
|
||||
sdv::pointer<uint8_t> Compose(const std::string& rssInstallName) const;
|
||||
|
||||
/**
|
||||
* @brief Compose the package to disk.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
|
||||
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
|
||||
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
|
||||
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
|
||||
* automatically extracted from the component and added to the installation manifest.
|
||||
* @param[in] rpathPackage Reference to the path receiving the package content. Any existing package will be overwritten.
|
||||
* @param[in] rssInstallName Reference to the string containing the installation name.
|
||||
* @return Returns whether the package composing was successful.
|
||||
*/
|
||||
bool Compose(const std::filesystem::path& rpathPackage, const std::string& rssInstallName) const;
|
||||
|
||||
/**
|
||||
* @brief Compose the installation directly at the target directory (without package composing and extracting).
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
|
||||
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
|
||||
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
|
||||
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
|
||||
* automatically extracted from the component and added to the installation manifest.
|
||||
* @param[in] rssInstallName Reference to the string containing the installation name.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
|
||||
* @return Returns the installation manifest when the package extraction was successful; or an empty manifest when not.
|
||||
*/
|
||||
CInstallManifest ComposeDirect(const std::string& rssInstallName, const std::filesystem::path& rpathInstallDir,
|
||||
EUpdateRules eUpdateRule = EUpdateRules::not_allowed) const;
|
||||
|
||||
/**
|
||||
* @brief Compose an installation manifest.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
|
||||
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
|
||||
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
|
||||
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
|
||||
* automatically extracted from the component and added to the installation manifest.
|
||||
* @param[in] rssInstallName Reference to the string containing the installation name.
|
||||
* @return Returns an initialized installation manifest if successful, or an empty manifest if not.
|
||||
*/
|
||||
CInstallManifest ComposeInstallManifest(const std::string& rssInstallName) const;
|
||||
|
||||
/**
|
||||
* @brief Extract a package to an installation directory.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @pre An installation directory must be available.
|
||||
* @param[in] rptrPackage Reference to the pointer containing the package content.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
|
||||
* @return Returns the installation manifest when the package extraction was successful; or an empty manifest when not.
|
||||
*/
|
||||
static CInstallManifest Extract(const sdv::pointer<uint8_t>& rptrPackage, const std::filesystem::path& rpathInstallDir,
|
||||
EUpdateRules eUpdateRule = EUpdateRules::not_allowed);
|
||||
|
||||
/**
|
||||
* @brief Extract a package to an installation directory.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @pre An installation directory must be available.
|
||||
* @param[in] rpathPackage Reference to the path of the package file.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
|
||||
* @return Returns the installation manifest when the package extraction was successful; or an empty manifest when not.
|
||||
*/
|
||||
static CInstallManifest Extract(const std::filesystem::path& rpathPackage, const std::filesystem::path& rpathInstallDir,
|
||||
EUpdateRules eUpdateRule = EUpdateRules::not_allowed);
|
||||
|
||||
/**
|
||||
* @brief Remove an installation.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @param[in] rssInstallName Reference to the string containing the installation name.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @return Returns the installation manifest of the removed installation when the removal was successful; or an empty manifest
|
||||
* when not.
|
||||
*/
|
||||
static CInstallManifest Remove(const std::string& rssInstallName, const std::filesystem::path& rpathInstallDir);
|
||||
|
||||
/**
|
||||
* @brief Verify the integrity of an installation package.
|
||||
* @throw Could throw a sdv::XSysExcept based exception with information about the integrity violation.
|
||||
* @param[in] rptrPackage Reference to the pointer containing the package content.
|
||||
* @return Returns 'true' when the package extraction was successful; 'false' when not.
|
||||
*/
|
||||
static bool Verify(const sdv::pointer<uint8_t>& rptrPackage);
|
||||
|
||||
/**
|
||||
* @brief Verify the integrity of an installation package.
|
||||
* @throw Could throw a sdv::XSysExcept based exception with information about the integrity violation.
|
||||
* @param[in] rpathPackage Reference to the path of the package file.
|
||||
* @return Returns 'true' when the package extraction was successful; 'false' when not.
|
||||
*/
|
||||
static bool Verify(const std::filesystem::path& rpathPackage);
|
||||
|
||||
/**
|
||||
* @brief Extract an installation manifest from a package.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @param[in] rptrPackage Reference to the pointer containing the package content.
|
||||
* @return Returns 'true' when the package extraction was successful; 'false' when not.
|
||||
*/
|
||||
static CInstallManifest ExtractInstallManifest(const sdv::pointer<uint8_t>& rptrPackage);
|
||||
|
||||
/**
|
||||
* @brief Extract an installation manifest from a package.
|
||||
* @throw Could throw a sdv::XSysExcept based exception.
|
||||
* @param[in] rpathPackage Reference to the path of the package file.
|
||||
* @return Returns 'true' when the package extraction was successful; 'false' when not.
|
||||
*/
|
||||
static CInstallManifest ExtractInstallManifest(const std::filesystem::path& rpathPackage);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief File entry (modules and companion files).
|
||||
*/
|
||||
struct SFileEntry
|
||||
{
|
||||
std::filesystem::path pathSrcModule; ///! Source path to the module.
|
||||
std::filesystem::path pathRelDir; ///< Relative directory within the installation.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Compose and serialize a package header.
|
||||
* @param[in, out] rptrPackage Reference to a pointer object receiving the serialized header. The pointer object will be
|
||||
* adjusted in size to fit the header.
|
||||
* @param[in] rmanifest Reference to the installation manifest to add.
|
||||
* @return The checksum of data stored in the package pointer.
|
||||
*/
|
||||
static uint32_t SerializePackageHeader(sdv::pointer<uint8_t>& rptrPackage, const CInstallManifest& rmanifest);
|
||||
|
||||
/**
|
||||
* @brief Serialize a module into a package BLOB.
|
||||
* @param[in] uiChecksumInit The initial checksum to start calculating the BLOB checksum with.
|
||||
* @param[in, out] rptrPackage Reference to a pointer object receiving the BLOB. The pointer object will be extended to fit
|
||||
* the BLOB, which will be placed following the data stored in the pointer buffer. The existing data will not be touched.
|
||||
* @param[in] rsFile Reference to the module entry to add as a BLOB.
|
||||
* @return The checksum of data stored in the package pointer.
|
||||
*/
|
||||
static uint32_t SerializeModuleBLOB(uint32_t uiChecksumInit, sdv::pointer<uint8_t>& rptrPackage, const SFileEntry& rsFile);
|
||||
|
||||
/**
|
||||
* @brief Serialize a final BLOB. This marks the end of the BLOBs within the package.
|
||||
* @param[in] uiChecksumInit The initial checksum to start calculating the BLOB checksum with.
|
||||
* @param[in, out] rptrPackage Reference to a pointer object receiving the BLOB. The pointer object will be extended to fit
|
||||
* the BLOB, which will be placed following the data stored in the pointer buffer. The existing data will not be touched.
|
||||
* @return The checksum of data stored in the package pointer.
|
||||
*/
|
||||
static uint32_t SerializeFinalBLOB(uint32_t uiChecksumInit, sdv::pointer<uint8_t>& rptrPackage);
|
||||
|
||||
/**
|
||||
* @brief Serialize the package footer.
|
||||
* @param[in] uiChecksum The checksum of the data to store in the footer.
|
||||
* @param[in, out] rptrPackage Reference to a pointer object receiving the footer. The pointer object will be extended to fit
|
||||
* the footer, which will be placed following the data stored in the pointer buffer. The existing data will not be touched.
|
||||
*/
|
||||
static void SerializePackageFooter(uint32_t uiChecksum, sdv::pointer<uint8_t>& rptrPackage);
|
||||
|
||||
/**
|
||||
* @brief Extracts the header from the file.
|
||||
* @details This function reads the header from the file from the current position and checks whether the content fits the
|
||||
* checksum.
|
||||
* @param[in, out] rfstream Reference to the opened package stream at the initial position.
|
||||
* @param[out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete header. This checksum
|
||||
* is used as input for the checksum of the following structures.
|
||||
* @return Returns the content of the package header.
|
||||
*/
|
||||
static sdv::installation::SPackageHeader DeserializeHeader(std::ifstream& rfstream, uint32_t& ruiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Extracts the header from the file.
|
||||
* @details This function reads the header from the file from the current position and checks whether the content fits the
|
||||
* checksum.
|
||||
* @param[in] rptrPackage Reference to the package containing the header.
|
||||
* @param[out] rnOffset Reference to the variable receiving the offset location following the header.
|
||||
* @param[out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete header. This checksum
|
||||
* is used as input for the checksum of the following structures.
|
||||
* @return Returns the content of the package header.
|
||||
*/
|
||||
static sdv::installation::SPackageHeader DeserializeHeader(const sdv::pointer<uint8_t>& rptrPackage, size_t& rnOffset,
|
||||
uint32_t& ruiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Extracts the BLOB from the file.
|
||||
* @details This function reads the BLOB from the file from the current position and checks whether the content fits the
|
||||
* checksum.
|
||||
* @param[in, out] rfstream Reference to the opened package stream at the current position.
|
||||
* @param[in, out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete BLOB. This
|
||||
* checksum is used as input for the checksum of the following structures.
|
||||
* @return Returns the content of the package header.
|
||||
*/
|
||||
static sdv::installation::SPackageBLOB DeserializeBLOB(std::ifstream& rfstream, uint32_t& ruiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Extracts the BLOB from the file.
|
||||
* @details This function reads the BLOB from the file from the current position and checks whether the content fits the
|
||||
* checksum.
|
||||
* @param[in] rptrPackage Reference to the package containing the BLOB.
|
||||
* @param[in, out] rnOffset Reference to the variable containing the offset to the BLOB and receiving the offset following the
|
||||
* BLOB.
|
||||
* @param[in, out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete BLOB. This
|
||||
* checksum is used as input for the checksum of the following structures.
|
||||
* @return Returns the content of the package header.
|
||||
*/
|
||||
static sdv::installation::SPackageBLOB DeserializeBLOB(const sdv::pointer<uint8_t>& rptrPackage, size_t& rnOffset, uint32_t& ruiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Check the final checksum using the package footer.
|
||||
* @param[in, out] rfstream Reference to the opened package stream at the current position.
|
||||
* @param[in] uiChecksum The calculated checksum of the package content to use for the calculation of the checksum of the
|
||||
* footer.
|
||||
*/
|
||||
static void DeserializeFinalChecksum(std::ifstream& rfstream, uint32_t uiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Check the final checksum using the package footer.
|
||||
* @param[in] rptrPackage Reference to the package containing the footer with the checksum.
|
||||
* @param[in] nOffset The variable containing the offset to the footer.
|
||||
* @param[in] uiChecksum The calculated checksum of the package content to use for the calculation of the checksum of the
|
||||
* footer.
|
||||
*/
|
||||
static void DeserializeFinalChecksum(const sdv::pointer<uint8_t>& rptrPackage, size_t nOffset, uint32_t uiChecksum);
|
||||
|
||||
/**
|
||||
* @brief Store the module from the supplied BLOB.
|
||||
* @param[in] rsModuleBLOB Reference to the BLOB structure containing the module. If the BLOB doesn't contain the module,
|
||||
* nothing occurs.
|
||||
* @param[in] rpathLocation Reference to the path containing the location to store the module to.
|
||||
*/
|
||||
static void StoreModuleBLOB(const sdv::installation::SPackageBLOB& rsModuleBLOB, const std::filesystem::path& rpathLocation);
|
||||
|
||||
/**
|
||||
* @brief Store the installation manifest at the provided location and set the creation time for the manifest file.
|
||||
* @param[in] uiCreationTime The time of the package creation (will be used for the manifest file).
|
||||
* @param[in] rpathLocation Reference to the path containing the location to store the manifest to.
|
||||
* @param[in] rmanifest Reference to the manifest to store.
|
||||
*/
|
||||
static void StoreManifest(const std::filesystem::path& rpathLocation, const CInstallManifest& rmanifest, int64_t uiCreationTime);
|
||||
|
||||
/**
|
||||
* @brief Set the file to read-only.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
*/
|
||||
static void SetReadOnly(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Get the file read-only-state.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
*/
|
||||
static bool IsReadOnly(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Set the file to executable.
|
||||
* @remarks Not available for Windows.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
*/
|
||||
static void SetExecutable(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Get the file to executable state.
|
||||
* @remarks Not available for WIndows.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
*/
|
||||
static bool IsExecutable(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Set the file creation time.
|
||||
* @remarks Only available for Windows.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
* @param[in] uiTimeMicrosec The unix epoch time in microseconds.
|
||||
*/
|
||||
static void SetCreateTime(const std::filesystem::path& rpathFile, uint64_t uiTimeMicrosec);
|
||||
|
||||
/**
|
||||
* @brief Get the file creation time.
|
||||
* @remarks Only available for Windows.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
* @return Returns the unix epoch time in microseconds.
|
||||
*/
|
||||
static uint64_t GetCreateTime(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Set the file change time.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
* @param[in] uiTimeMicrosec The unix epoch time in microseconds.
|
||||
*/
|
||||
static void SetChangeTime(const std::filesystem::path& rpathFile, uint64_t uiTimeMicrosec);
|
||||
|
||||
/**
|
||||
* @brief Get the file change time.
|
||||
* @param[in] rpathFile Reference to the path of the file.
|
||||
* @return Returns the unix epoch time in microseconds.
|
||||
*/
|
||||
static uint64_t GetChangeTime(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Check whether thr installed version can be overwritten. This is the case when the version number of the current
|
||||
* package is larger than the version number of the installed package.
|
||||
* @remarks Returns 'true' if the current installation doesn't have an installation manifest.
|
||||
* @remarks If there is no version stored in the installation manifest, the version number is considered 0.0.0.
|
||||
* @param[in] rpathInstall Reference to the installation directory of the current installation. This directory must contain a
|
||||
* "install_manifest.toml" file.
|
||||
* @param[in] sVersionNew The version to use for comparison.
|
||||
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
|
||||
* @return Returns whether the existing installation can be updated (removed and installed again).
|
||||
*/
|
||||
static bool UpdateExistingInstallation(const std::filesystem::path& rpathInstall, sdv::installation::SPackageVersion sVersionNew, EUpdateRules eUpdateRule);
|
||||
|
||||
std::list<SFileEntry> m_lstFiles; ///< List of modules added to the package
|
||||
std::map<std::string, std::string> m_mapProperties; ///< Property map.
|
||||
};
|
||||
|
||||
#endif // !defined INSTALL_PACKAGING_H
|
||||
478
sdv_services/core/installation_manifest.cpp
Normal file
478
sdv_services/core/installation_manifest.cpp
Normal file
@@ -0,0 +1,478 @@
|
||||
#include "installation_manifest.h"
|
||||
#include "toml_parser/parser_toml.h"
|
||||
#include <support/serdes.h>
|
||||
|
||||
#if defined _WIN32 && defined __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
||||
#endif
|
||||
|
||||
#ifdef __unix__
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Read the module manifest from the binary.
|
||||
* @param[in] rpathModule Reference to the module to read the manifest from.
|
||||
* @return The manifest, if existing or an empty string when not.
|
||||
*/
|
||||
std::string ReadModuleManifest(const std::filesystem::path& rpathModule)
|
||||
{
|
||||
if (rpathModule.extension() != ".sdv") return {};
|
||||
if (!std::filesystem::exists(rpathModule)) return {};
|
||||
|
||||
// Load the module
|
||||
#ifdef _WIN32
|
||||
sdv::core::TModuleID tModuleID = reinterpret_cast<sdv::core::TModuleID>(LoadLibrary(rpathModule.native().c_str()));
|
||||
#elif defined __unix__
|
||||
sdv::core::TModuleID tModuleID = reinterpret_cast<sdv::core::TModuleID>(dlopen(rpathModule.native().c_str(), RTLD_LAZY));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
if (!tModuleID) return {};
|
||||
|
||||
// Check whether the module exposes the necessary functions
|
||||
using TFNHasActiveObjects = bool();
|
||||
using TFNGetModuleFactory = sdv::IInterfaceAccess*(uint32_t);
|
||||
using TFNGetManifest = const char*();
|
||||
|
||||
#ifdef _WIN32
|
||||
std::function<TFNGetModuleFactory> fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "GetModuleFactory"));
|
||||
std::function<TFNHasActiveObjects> fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "HasActiveObjects"));
|
||||
std::function<TFNGetManifest> fnGetManifest = reinterpret_cast<TFNGetManifest*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "GetManifest"));
|
||||
#elif defined __unix__
|
||||
std::function<TFNGetModuleFactory> fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(dlsym(reinterpret_cast<void*>(tModuleID), "GetModuleFactory"));
|
||||
std::function<TFNHasActiveObjects> fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(dlsym(reinterpret_cast<void*>(tModuleID), "HasActiveObjects"));
|
||||
std::function<TFNGetManifest> fnGetManifest = reinterpret_cast<TFNGetManifest*>(dlsym(reinterpret_cast<void*>(tModuleID), "GetManifest"));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
|
||||
// Check for functions and the correct version
|
||||
std::string ssManifest;
|
||||
if (fnGetFactory && fnActiveObjects && fnGetManifest && fnGetManifest())
|
||||
ssManifest = fnGetManifest();
|
||||
|
||||
// Release the library
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(reinterpret_cast<HMODULE>(tModuleID));
|
||||
#elif defined __unix__
|
||||
dlclose(reinterpret_cast<void*>(tModuleID));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
|
||||
// Return the manifest
|
||||
return ssManifest;
|
||||
}
|
||||
|
||||
bool CInstallManifest::IsValid() const
|
||||
{
|
||||
// Must have an installation name to be valid.
|
||||
return !m_ssInstallName.empty();
|
||||
}
|
||||
|
||||
void CInstallManifest::Clear()
|
||||
{
|
||||
m_ssInstallName.clear();
|
||||
m_vecModules.clear();
|
||||
m_bBlockSystemObjects = false;
|
||||
}
|
||||
|
||||
const std::string& CInstallManifest::InstallName() const
|
||||
{
|
||||
return m_ssInstallName;
|
||||
}
|
||||
|
||||
sdv::installation::SPackageVersion CInstallManifest::Version() const
|
||||
{
|
||||
auto optProperty = Property("Version");
|
||||
if (!optProperty) return {};
|
||||
|
||||
// A property string is composed of: major.minor.patch (numbers only; characters and whitespace are ignored).
|
||||
sdv::installation::SPackageVersion sVersion{};
|
||||
size_t nPos = optProperty->find('.');
|
||||
sVersion.uiMajor = static_cast<uint32_t>(std::atoi(optProperty->substr(0, nPos).c_str()));
|
||||
size_t nStart = nPos;
|
||||
if (nStart != std::string::npos)
|
||||
{
|
||||
nStart++;
|
||||
nPos = optProperty->find('.', nPos + 1);
|
||||
sVersion.uiMinor = static_cast<uint32_t>(std::atoi(optProperty->substr(nStart, nPos).c_str()));
|
||||
}
|
||||
nStart = nPos;
|
||||
if (nStart != std::string::npos)
|
||||
{
|
||||
nStart++;
|
||||
sVersion.uiPatch = static_cast<uint32_t>(std::atoi(optProperty->substr(nStart).c_str()));
|
||||
}
|
||||
return sVersion;
|
||||
}
|
||||
|
||||
const std::filesystem::path& CInstallManifest::InstallDir() const
|
||||
{
|
||||
return m_pathInstallDir;
|
||||
}
|
||||
|
||||
bool CInstallManifest::Create(const std::string& rssInstallName)
|
||||
{
|
||||
Clear();
|
||||
|
||||
m_ssInstallName = rssInstallName;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CInstallManifest::Load(const std::filesystem::path& rpathInstallDir, bool bBlockSystemObjects /*= false*/)
|
||||
{
|
||||
if (rpathInstallDir.empty() || !std::filesystem::exists(rpathInstallDir) ||
|
||||
!std::filesystem::is_directory(rpathInstallDir)) return {};
|
||||
m_pathInstallDir = rpathInstallDir;
|
||||
|
||||
// Create the manifest file
|
||||
std::ifstream fstream(rpathInstallDir / "install_manifest.toml");
|
||||
if (!fstream.is_open()) return false;
|
||||
|
||||
// Read the manifest in memory
|
||||
std::stringstream sstreamManifest;
|
||||
sstreamManifest << fstream.rdbuf();
|
||||
fstream.close();
|
||||
|
||||
return Read(sstreamManifest.str(), bBlockSystemObjects);
|
||||
}
|
||||
|
||||
bool CInstallManifest::Save(const std::filesystem::path& rpathInstallDir) const
|
||||
{
|
||||
if (!IsValid() || rpathInstallDir.empty()) return false;
|
||||
m_pathInstallDir = rpathInstallDir;
|
||||
|
||||
// Create the manifest file
|
||||
std::ofstream fstream(rpathInstallDir / "install_manifest.toml", std::ios_base::out | std::ios_base::trunc);
|
||||
if (!fstream.is_open()) return false;
|
||||
|
||||
std::string ssManifest = Write();
|
||||
if (!ssManifest.empty()) fstream << ssManifest;
|
||||
|
||||
fstream.close();
|
||||
|
||||
return !ssManifest.empty();
|
||||
}
|
||||
|
||||
bool CInstallManifest::Read(const std::string& rssManifest, bool bBlockSystemObjects /*= false*/)
|
||||
{
|
||||
Clear();
|
||||
m_bBlockSystemObjects = bBlockSystemObjects;
|
||||
|
||||
// Parse the manifest
|
||||
CParserTOML parser(rssManifest);
|
||||
|
||||
// Get the installation version - must be identical to the interface version
|
||||
auto ptrInstallVersionNode = parser.GetRoot().GetDirect("Installation.Version");
|
||||
if (!ptrInstallVersionNode || ptrInstallVersionNode->GetValue() != SDVFrameworkInterfaceVersion) return false;
|
||||
|
||||
// Get the installation name
|
||||
auto ptrInstallNameNode = parser.GetRoot().GetDirect("Installation.Name");
|
||||
if (!ptrInstallNameNode) return false;
|
||||
m_ssInstallName = static_cast<std::string>(ptrInstallNameNode->GetValue());
|
||||
if (m_ssInstallName.empty()) return false;
|
||||
|
||||
// Get installation properties. The properties are optional
|
||||
auto ptrProperties = parser.GetRoot().GetDirect("Properties");
|
||||
std::shared_ptr<CTable> ptrPropertyTable;
|
||||
if (ptrProperties) ptrPropertyTable = ptrProperties->GetTable();
|
||||
if (ptrPropertyTable)
|
||||
{
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrPropertyTable->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrProperty = ptrPropertyTable->Get(uiIndex);
|
||||
if (ptrProperty)
|
||||
m_mapProperties[ptrProperty->GetName()] = static_cast<std::string>(ptrProperty->GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Build the module list
|
||||
auto ptrModulesNode = parser.GetRoot().GetDirect("Module");
|
||||
if (!ptrModulesNode) return true; // No modules in the manifest
|
||||
auto ptrModuleArrayNode = ptrModulesNode->GetArray();
|
||||
if (!ptrModuleArrayNode) return false; // Must be array
|
||||
for (uint32_t uiModuleIndex = 0; uiModuleIndex < ptrModuleArrayNode->GetCount(); uiModuleIndex++)
|
||||
{
|
||||
// Get the module
|
||||
auto ptrModule = ptrModuleArrayNode->Get(uiModuleIndex);
|
||||
if (!ptrModule) continue;
|
||||
|
||||
// Get the module path
|
||||
auto ptrModulePath = ptrModule->GetDirect("Path");
|
||||
if (!ptrModulePath) continue;
|
||||
std::filesystem::path pathModule = static_cast<std::string>(ptrModulePath->GetValue());
|
||||
std::string ssModuleManifest;
|
||||
|
||||
// Get the component list (if available)
|
||||
auto ptrModuleComponents = ptrModule->GetDirect("Component");
|
||||
if (ptrModuleComponents)
|
||||
{
|
||||
// The module manifest contains the TOML text of the component array
|
||||
auto ptrModuleComponentArray = ptrModuleComponents->GetArray();
|
||||
if (ptrModuleComponentArray)
|
||||
ssModuleManifest = ptrModuleComponents->CreateTOMLText();
|
||||
}
|
||||
|
||||
// Add the module
|
||||
m_vecModules.push_back(SModule(pathModule, ssModuleManifest,
|
||||
m_bBlockSystemObjects));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string CInstallManifest::Write() const
|
||||
{
|
||||
if (!IsValid()) return {};
|
||||
|
||||
std::stringstream sstream;
|
||||
|
||||
// Add the installation section
|
||||
sstream << "[Installation]" << std::endl << "Version = " << SDVFrameworkSubbuildVersion << std::endl << "Name = \"" <<
|
||||
m_ssInstallName << "\"" << std::endl << std::endl;
|
||||
|
||||
// Add the properties section (if there are any properties)
|
||||
if (!m_mapProperties.empty())
|
||||
{
|
||||
sstream << "[Properties]" << std::endl;
|
||||
for (const auto& rvtProperty : m_mapProperties)
|
||||
{
|
||||
if (NeedQuotedName(rvtProperty.first))
|
||||
sstream << "\"" << rvtProperty.first << "\"";
|
||||
else
|
||||
sstream << rvtProperty.first;
|
||||
sstream << " = \"" << rvtProperty.second << "\"" << std::endl;
|
||||
}
|
||||
sstream << std::endl;
|
||||
}
|
||||
|
||||
// Add the modules
|
||||
for (const SModule& rsEntry : m_vecModules)
|
||||
{
|
||||
sstream << "[[Module]]" << std::endl << "Path=\"" << rsEntry.pathRelModule.generic_u8string() << "\"" << std::endl;
|
||||
|
||||
// Read the module manifest
|
||||
CParserTOML parser(rsEntry.ssManifest);
|
||||
|
||||
// Add the module manifest as part of the installation manifest.
|
||||
sstream << parser.CreateTOMLText("Module") << std::endl;
|
||||
}
|
||||
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
bool CInstallManifest::AddModule(const std::filesystem::path& rpathModulePath,
|
||||
const std::filesystem::path& rpathRelTargetDir /*= "."*/)
|
||||
{
|
||||
if (!IsValid()) return false;
|
||||
|
||||
// Check for the existence of the module.
|
||||
if (!std::filesystem::exists(rpathModulePath)) return false;
|
||||
if (!std::filesystem::is_regular_file(rpathModulePath)) return false;
|
||||
|
||||
// Check for a relative path
|
||||
if (!rpathRelTargetDir.is_relative()) return false;
|
||||
if (RefersToRelativeParent(rpathRelTargetDir)) return false;
|
||||
|
||||
// Read the manifest...
|
||||
std::string ssManifest = ReadModuleManifest(rpathModulePath);
|
||||
|
||||
// Read the component manifest if existing
|
||||
std::string ssComponentsManifest;
|
||||
if (!ssManifest.empty())
|
||||
{
|
||||
// Check the interface version for compatibility
|
||||
CParserTOML parser(ssManifest);
|
||||
auto ptrInterfaceNode = parser.GetRoot().GetDirect("Interface.Version");
|
||||
if (!ptrInterfaceNode) return false;
|
||||
if (ptrInterfaceNode->GetValue() != SDVFrameworkInterfaceVersion) return false;
|
||||
auto ptrComponentsNode = parser.GetRoot().GetDirect("Component");
|
||||
if (!ptrComponentsNode) return true; // No component available in the manifest
|
||||
if (!ptrComponentsNode->GetArray()) return false;
|
||||
ssComponentsManifest = ptrComponentsNode->CreateTOMLText();
|
||||
}
|
||||
|
||||
// Store path and component
|
||||
std::filesystem::path pathRelModule = (rpathRelTargetDir / rpathModulePath.filename()).lexically_normal();
|
||||
m_vecModules.push_back(SModule(pathRelModule,
|
||||
ssComponentsManifest, m_bBlockSystemObjects));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::filesystem::path CInstallManifest::FindModule(const std::filesystem::path& rpathRelModule) const
|
||||
{
|
||||
if (!IsValid() || m_pathInstallDir.empty())
|
||||
return {};
|
||||
|
||||
// Search for the correct module
|
||||
auto itModule = std::find_if(
|
||||
m_vecModules.begin(), m_vecModules.end(), [&](const SModule& rsEntry) { return rsEntry.pathRelModule == rpathRelModule; });
|
||||
if (itModule != m_vecModules.end())
|
||||
return m_pathInstallDir / rpathRelModule;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string CInstallManifest::FindModuleManifest(const std::filesystem::path& rpathRelModule) const
|
||||
{
|
||||
if (!IsValid() || m_pathInstallDir.empty())
|
||||
return {};
|
||||
|
||||
// Search for the correct module
|
||||
auto itModule = std::find_if(
|
||||
m_vecModules.begin(), m_vecModules.end(), [&](const SModule& rsEntry) { return rsEntry.pathRelModule == rpathRelModule; });
|
||||
if (itModule != m_vecModules.end())
|
||||
return itModule->ssManifest;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<CInstallManifest::SComponent> CInstallManifest::FindComponentByClass(const std::string& rssClass) const
|
||||
{
|
||||
// Search for the correct module
|
||||
SComponent sRet{};
|
||||
auto itModule = std::find_if(m_vecModules.begin(), m_vecModules.end(), [&](const SModule& rsEntry)
|
||||
{
|
||||
return std::find_if(rsEntry.vecComponents.begin(), rsEntry.vecComponents.end(),
|
||||
[&](const SComponent& sComponent)
|
||||
{
|
||||
// Note, use the class, alias and the default object name for searching...
|
||||
if (sComponent.ssClassName == rssClass ||
|
||||
std::find(sComponent.seqAliases.begin(), sComponent.seqAliases.end(), rssClass) !=
|
||||
sComponent.seqAliases.end())
|
||||
{
|
||||
sRet = sComponent;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}) != rsEntry.vecComponents.end();
|
||||
});
|
||||
if (itModule != m_vecModules.end()) return sRet;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<CInstallManifest::SComponent> CInstallManifest::ComponentList() const
|
||||
{
|
||||
std::vector<SComponent> vecComponents;
|
||||
for (const auto& rsModule : m_vecModules)
|
||||
{
|
||||
for (const auto& rsComponent : rsModule.vecComponents)
|
||||
vecComponents.push_back(rsComponent);
|
||||
}
|
||||
return vecComponents;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> CInstallManifest::ModuleList() const
|
||||
{
|
||||
std::vector<std::filesystem::path> vecModules;
|
||||
for (const auto& rsModule : m_vecModules)
|
||||
vecModules.push_back(rsModule.pathRelModule);
|
||||
return vecModules;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> CInstallManifest::PropertyList() const
|
||||
{
|
||||
return std::vector<std::pair<std::string, std::string>>(m_mapProperties.begin(), m_mapProperties.end());
|
||||
}
|
||||
|
||||
void CInstallManifest::Property(const std::string& rssName, const std::string& rssValue)
|
||||
{
|
||||
// Check for a quote in the name... if existing then do not allow insertion.
|
||||
if (rssName.find_first_of("\"'") != std::string::npos)
|
||||
return;
|
||||
m_mapProperties[rssName] = rssValue;
|
||||
}
|
||||
|
||||
std::optional<std::string> CInstallManifest::Property(const std::string& rssName) const
|
||||
{
|
||||
auto itProperty = m_mapProperties.find(rssName);
|
||||
if (itProperty == m_mapProperties.end())
|
||||
return {};
|
||||
return itProperty->second;
|
||||
}
|
||||
|
||||
bool CInstallManifest::NeedQuotedName(const std::string& rssName)
|
||||
{
|
||||
for (char c : rssName)
|
||||
{
|
||||
if (!std::isalnum(c) && c != '_' && c != '-')
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CInstallManifest::SModule::SModule(const std::filesystem::path& rpathRelModule, const std::string& rssManifest,
|
||||
bool bBlockSystemObjects) : pathRelModule(rpathRelModule), ssManifest(rssManifest)
|
||||
{
|
||||
// Parse the manifest and extract information from them...
|
||||
CParserTOML parser(rssManifest);
|
||||
auto ptrComponents = parser.GetRoot().GetDirect("Component");
|
||||
if (!ptrComponents) return; // No objects...
|
||||
auto ptrComponentArray = ptrComponents->GetArray();
|
||||
if (!ptrComponentArray) return; // No objects...
|
||||
for (uint32_t uiIndex = 0; uiIndex < ptrComponentArray->GetCount(); uiIndex++)
|
||||
{
|
||||
auto ptrComponent = ptrComponentArray->Get(uiIndex);
|
||||
if (!ptrComponent) continue;
|
||||
|
||||
// Fill in the component structure
|
||||
SComponent sComponent{};
|
||||
//sComponent.pathModule = rpathModule;
|
||||
sComponent.pathRelModule = rpathRelModule;
|
||||
sComponent.ssManifest = ptrComponent->CreateTOMLText("Component");
|
||||
auto ptrClassName = ptrComponent->GetDirect("Class");
|
||||
if (!ptrClassName) continue;
|
||||
sComponent.ssClassName = static_cast<std::string>(ptrClassName->GetValue());
|
||||
auto ptrAliases = ptrComponent->GetDirect("Aliases");
|
||||
if (ptrAliases)
|
||||
{
|
||||
auto ptrAliasesArray = ptrAliases->GetArray();
|
||||
for (uint32_t uiAliasIndex = 0; ptrAliasesArray && uiAliasIndex < ptrAliasesArray->GetCount(); uiAliasIndex++)
|
||||
{
|
||||
auto ptrClassAlias = ptrAliasesArray->Get(uiAliasIndex);
|
||||
if (ptrClassAlias)
|
||||
sComponent.seqAliases.push_back(static_cast<sdv::u8string>(ptrClassAlias->GetValue()));
|
||||
}
|
||||
}
|
||||
auto ptrDefaultName = ptrComponent->GetDirect("DefaultName");
|
||||
if (ptrDefaultName) sComponent.ssDefaultObjectName = static_cast<std::string>(ptrDefaultName->GetValue());
|
||||
else sComponent.ssDefaultObjectName = sComponent.ssClassName;
|
||||
auto ptrType = ptrComponent->GetDirect("Type");
|
||||
if (!ptrType) continue;
|
||||
std::string ssType = static_cast<std::string>(ptrType->GetValue());
|
||||
if (ssType == "System") sComponent.eType = sdv::EObjectType::SystemObject;
|
||||
else if (ssType == "Device") sComponent.eType = sdv::EObjectType::Device;
|
||||
else if (ssType == "BasicService") sComponent.eType = sdv::EObjectType::BasicService;
|
||||
else if (ssType == "ComplexService") sComponent.eType = sdv::EObjectType::ComplexService;
|
||||
else if (ssType == "App") sComponent.eType = sdv::EObjectType::Application;
|
||||
else if (ssType == "Proxy") sComponent.eType = sdv::EObjectType::Proxy;
|
||||
else if (ssType == "Stub") sComponent.eType = sdv::EObjectType::Stub;
|
||||
else if (ssType == "Utility") sComponent.eType = sdv::EObjectType::Utility;
|
||||
else continue;
|
||||
if (bBlockSystemObjects && sComponent.eType == sdv::EObjectType::SystemObject) continue;
|
||||
auto ptrSingleton = ptrComponent->GetDirect("Singleton");
|
||||
if (ptrSingleton && static_cast<bool>(ptrSingleton->GetValue()))
|
||||
sComponent.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
|
||||
auto ptrDependencies = ptrComponent->GetDirect("Dependencies");
|
||||
if (ptrDependencies)
|
||||
{
|
||||
auto ptrDependencyArray = ptrDependencies->GetArray();
|
||||
for (uint32_t uiDependencyIndex = 0; ptrDependencyArray && uiDependencyIndex < ptrDependencyArray->GetCount();
|
||||
uiDependencyIndex++)
|
||||
{
|
||||
auto ptrDependsOn = ptrDependencyArray->Get(uiDependencyIndex);
|
||||
if (ptrDependsOn)
|
||||
sComponent.seqDependencies.push_back(static_cast<sdv::u8string>(ptrDependsOn->GetValue()));
|
||||
}
|
||||
}
|
||||
|
||||
vecComponents.push_back(sComponent);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined _WIN32 && defined __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
402
sdv_services/core/installation_manifest.h
Normal file
402
sdv_services/core/installation_manifest.h
Normal file
@@ -0,0 +1,402 @@
|
||||
#ifndef INSTALL_MANIFEST_H
|
||||
#define INSTALL_MANIFEST_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <interfaces/core.h>
|
||||
#include <interfaces/config.h>
|
||||
#include <map>
|
||||
#include <cstdlib>
|
||||
|
||||
/**
|
||||
* @brief Check whether a relative path is directing to a parent of the base path.
|
||||
* @details Detect whether a relative path joined to the base path is referring to a parent of the base path. For example, the
|
||||
* base path is "/home/user/project" and the relative path is "..". Both joined together would make "/home/user", which is a parent
|
||||
* of the base path.
|
||||
* @param[in] rpathBase Reference to the base path.
|
||||
* @param[in] rpathRelative Reference to the relative path following the base.
|
||||
* @return Returns whether the relative path is a parent of the base (not the base or a child of the base).
|
||||
*/
|
||||
inline bool IsParentPath(const std::filesystem::path& rpathBase, const std::filesystem::path& rpathRelative)
|
||||
{
|
||||
auto pathAbsBase = std::filesystem::weakly_canonical(rpathBase);
|
||||
auto pathAbsPotentialParent = std::filesystem::weakly_canonical(rpathBase / rpathRelative);
|
||||
|
||||
// Check if pathAbsPotentialParent supersedes pathAbsBase. If not, it points to or derives from a parent.
|
||||
auto itBase = pathAbsBase.begin();
|
||||
auto itPotentialParent = pathAbsPotentialParent.begin();
|
||||
while (itBase != pathAbsBase.end())
|
||||
{
|
||||
// If the potential path has finished, it is pointing to a parent.
|
||||
if (itPotentialParent == pathAbsPotentialParent.end()) return true;
|
||||
|
||||
// If the path-parts are not equal, the potential parent part is really deriving from a parent.
|
||||
if (*itBase != *itPotentialParent) return true;
|
||||
|
||||
// Check next
|
||||
itBase++;
|
||||
itPotentialParent++;
|
||||
}
|
||||
|
||||
// Even if the potential path might be still have more parts, they derive from the base path.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether the relative path directs to a parent path.
|
||||
* @details Detect whether a relative path directs to a parent path. For example with "./dir1/dir2/../dir3"; this is not the case,
|
||||
* but with "./dir1/dir2/../../../dir3" this is the case.
|
||||
* @param[in] rpathRelative Reference to the relative path to check for.
|
||||
* @return Returns whether the relative path refers to the
|
||||
*/
|
||||
inline bool RefersToRelativeParent(const std::filesystem::path& rpathRelative)
|
||||
{
|
||||
int iDepth = 0;
|
||||
for (const auto& pathPart : rpathRelative)
|
||||
{
|
||||
if (pathPart == "..")
|
||||
--iDepth;
|
||||
else if (pathPart != "." && pathPart != "")
|
||||
++iDepth;
|
||||
if (iDepth < 0) return true; // Not allowed to be negative.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Class managing the installation manifest.
|
||||
* @details The installation manifest is a TOML file with the name "install_manifest.toml" and the following format:
|
||||
* @code
|
||||
* [Installation]
|
||||
* Version = 100 # Installation manifest version.
|
||||
* Name = "Duck" # Name of the installation. This typically is identical to the installation directory name.
|
||||
*
|
||||
* [Properties]
|
||||
* Product = "Wild goose components" # Product name
|
||||
* Description = "Mallard installation" # Description
|
||||
* Author = "Nils Holgerson" # Author
|
||||
* Address = """Vildgåsvägen 7
|
||||
* Skanör med Falsterbo
|
||||
* Skåne län, 123 45
|
||||
* Sverige""" # Address
|
||||
* Copyrights = "(C) 2025 Wild goose" # Copyrights
|
||||
* Version = "0.1.2.3" # Package version
|
||||
*
|
||||
* [[Module]]
|
||||
* Path = "mallard.sdv # Relative path to the module
|
||||
*
|
||||
* [[Module.Component]] # Component manifest
|
||||
* Class = "Mallard class" # The name of the class
|
||||
* Aliases = ["Duck", "Pont duck"] # Optional list of aliases
|
||||
* DefaultName = "Duck" # Optional default name for the class instance
|
||||
* Type = "Complex service" # Component type (Device, BasicService, ComplexService, App, Proxy, Stub, Utility)
|
||||
* Singleton = false # Optional singleton flag
|
||||
* Dependencies = ["Bird", "Animal"] # Optional list of dependencies
|
||||
*
|
||||
* [[Module.Component]] # Another component manifest
|
||||
* #...
|
||||
*
|
||||
* [[Module]] # Another module
|
||||
* Path = "large/greylag_goose.sdv # Relative path to the module
|
||||
* @endcode
|
||||
* @remarks The installation directory path is used to create relative paths to the modules. It is not stored in the manifest
|
||||
* itself allowing the manifest to be copied from one location to another as long as the relative path to the modules is maintained
|
||||
* (meaning copying the modules along with the manifest).
|
||||
*/
|
||||
class CInstallManifest
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Manifest information belonging to the component.
|
||||
*/
|
||||
struct SComponent
|
||||
{
|
||||
std::filesystem::path pathRelModule; ///< Relative module path (relative to the installation directory).
|
||||
std::string ssManifest; ///< Component manifest.
|
||||
std::string ssClassName; ///< String representing the class name.
|
||||
sdv::sequence<sdv::u8string> seqAliases; ///< Sequence containing zero or more class name aliases.
|
||||
std::string ssDefaultObjectName; ///< The default object name.
|
||||
sdv::EObjectType eType; ///< Type of object.
|
||||
uint32_t uiFlags; ///< Zero or more object flags from EObjectFlags.
|
||||
sdv::sequence<sdv::u8string> seqDependencies; ///< This component depends on...
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Default constructor.
|
||||
*/
|
||||
CInstallManifest() = default;
|
||||
|
||||
/**
|
||||
* @brief Is this a valid installation manifest (installation directory path is known and a name is given)?
|
||||
* @return Returns whether the installation manifest is valid.
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* @brief Clear the current manifest to start a new manifest.
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* @brief Get the installation name.
|
||||
* @return Reference to the string holding the installation name.
|
||||
*/
|
||||
const std::string& InstallName() const;
|
||||
|
||||
/**
|
||||
* @brief Get the package version from the version property.
|
||||
* @remarks The version property can hold additional information, which is not available through this function.
|
||||
* @return Structure containing the version information.
|
||||
*/
|
||||
sdv::installation::SPackageVersion Version() const;
|
||||
|
||||
/**
|
||||
* @brief Get the installation directory path.
|
||||
* @return Reference to the path to the installation.
|
||||
*/
|
||||
const std::filesystem::path& InstallDir() const;
|
||||
|
||||
/**
|
||||
* @brief Create a new manifest.
|
||||
* @param[in] rssInstallName Reference to the string containing the installaltion name.
|
||||
* @return Returns whether manifest creation was successful.
|
||||
*/
|
||||
bool Create(const std::string& rssInstallName);
|
||||
|
||||
/**
|
||||
* @brief Load a manifest TOML file.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @param[in] bBlockSystemObjects When set, system objects are not stored in the repository.
|
||||
* @return Returns whether loading the TOML file was successful.
|
||||
*/
|
||||
bool Load(const std::filesystem::path& rpathInstallDir, bool bBlockSystemObjects = false);
|
||||
|
||||
/**
|
||||
* @brief Save a manifest TOML file.
|
||||
* @param[in] rpathInstallDir Reference to the installation directory.
|
||||
* @return Returns whether saving the TOML file was successful.
|
||||
*/
|
||||
bool Save(const std::filesystem::path& rpathInstallDir) const;
|
||||
|
||||
/**
|
||||
* @brief Read a manifest TOML string.
|
||||
* @param[in] rssManifest Reference to the string containing the manifest.
|
||||
* @param[in] bBlockSystemObjects When set, system objects are not stored in the repository.
|
||||
* @return Returns whether reading the TOML string was successful.
|
||||
*/
|
||||
bool Read(const std::string& rssManifest, bool bBlockSystemObjects = false);
|
||||
|
||||
/**
|
||||
* @brief Write a manifest TOML string.
|
||||
* @return The manifest TOML string when successful or an empty string when not.
|
||||
*/
|
||||
std::string Write() const;
|
||||
|
||||
/**
|
||||
* @brief Add a module to the installation manifest (if the module contains components).
|
||||
* @attention This function should not be called by the core application, since it imposes a security risk!
|
||||
* @param[in] rpathModulePath Reference to the module path.
|
||||
* @param[in] rpathRelTargetDir The relative target directory the module should be stored at.
|
||||
* @return Returns 'true' when successful; 'false' when not.
|
||||
*/
|
||||
bool AddModule(const std::filesystem::path& rpathModulePath, const std::filesystem::path& rpathRelTargetDir = ".");
|
||||
|
||||
/**
|
||||
* @brief Find the module stored in the installation manifest.
|
||||
* @pre Only successful for manifests having an installation directory.
|
||||
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
|
||||
* @return Returns the full path if the module was found or an empty path when not.
|
||||
*/
|
||||
std::filesystem::path FindModule(const std::filesystem::path& rpathRelModule) const;
|
||||
|
||||
/**
|
||||
* @brief Find the module manifest.
|
||||
* @pre Only successful for manifests having an installation directory.
|
||||
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
|
||||
* @return Returns the string containing the module manifest.
|
||||
*/
|
||||
std::string FindModuleManifest(const std::filesystem::path& rpathRelModule) const;
|
||||
|
||||
/**
|
||||
* @brief Find the component stored in this installation manifest.
|
||||
* @param[in] rssClass Reference to the class name of the component.
|
||||
* @return The component manifest information.
|
||||
*/
|
||||
std::optional<SComponent> FindComponentByClass(const std::string& rssClass) const;
|
||||
|
||||
/**
|
||||
* @brief Get a vector of all components stored in this installation manifest.
|
||||
* @return The component manifest vector.
|
||||
*/
|
||||
std::vector<SComponent> ComponentList() const;
|
||||
|
||||
/**
|
||||
* @brief Get the module list.
|
||||
* @return Returns a vector with paths to all modules relative to the installation directory.
|
||||
*/
|
||||
std::vector<std::filesystem::path> ModuleList() const;
|
||||
|
||||
/**
|
||||
* @brief Get the property list.
|
||||
* @return Returns a vector with properties and the corresponding values.
|
||||
*/
|
||||
std::vector<std::pair<std::string, std::string>> PropertyList() const;
|
||||
|
||||
/**
|
||||
* @brief Set a property value.
|
||||
* @details Set a property value, which will be included in the installation manifest. The properties "Description" and
|
||||
* "Version" are used during package management.
|
||||
* @remarks Adding a property with the same name as a previously added property will replace the previous property value.
|
||||
* @param[in] rssName Name of the property. Spaces are allowed. Quotes are not allowed.
|
||||
* @param[in] rssValue Property value.
|
||||
*/
|
||||
void Property(const std::string& rssName, const std::string& rssValue);
|
||||
|
||||
/**
|
||||
* @brief Get a property value.
|
||||
* @param[in] rssName Name of the property. Spaces are allowed. Quotes are not allowed.
|
||||
* @return Returns the property value if existing.
|
||||
*/
|
||||
std::optional<std::string> Property(const std::string& rssName) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Checks the name for adherence to the bare key rule of TOML. If a bare key is not possible, a quoted key is required.
|
||||
* @remarks TOML defines for bare keys that they can only be composed of ASCII letters, ASCII digits, underscores and dashes.
|
||||
* @param[in] rssName Name to check for.
|
||||
* @return Returns whether quotes are required.
|
||||
*/
|
||||
static bool NeedQuotedName(const std::string& rssName);
|
||||
|
||||
/**
|
||||
* @brief Manifest information belonging to the module.
|
||||
*/
|
||||
struct SModule
|
||||
{
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rpathRelModule Reference to the relative module path.
|
||||
* @param[in] rssManifest Reference to the manifest file.
|
||||
* @param[in] bBlockSystemObjects Set when to block system objects.
|
||||
*/
|
||||
SModule(const std::filesystem::path& rpathRelModule, const std::string& rssManifest, bool bBlockSystemObjects);
|
||||
|
||||
std::filesystem::path pathRelModule; ///< Relative module path (relative to the installation directory).
|
||||
std::string ssManifest; ///< Manifest containing the components.
|
||||
std::vector<SComponent> vecComponents; ///< Vector with contained components
|
||||
};
|
||||
|
||||
std::string m_ssInstallName; ///< Installation name.
|
||||
mutable std::filesystem::path m_pathInstallDir; ///< Installation directory when install manifest was
|
||||
///< loaded or saved.
|
||||
bool m_bBlockSystemObjects = false; ///< When set, do not store system objects.
|
||||
std::vector<SModule> m_vecModules; ///< Vector containing the modules.
|
||||
std::map<std::string, std::string> m_mapProperties; ///< Property map.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Interpret a version number as string.
|
||||
* @details A version string is composed of: major.minor.patch (numbers only; characters and whitespace are ignored).
|
||||
* @param rssVersion Reference to the version string.
|
||||
* @return The interpreted version.
|
||||
*/
|
||||
inline sdv::installation::SPackageVersion InterpretVersionString(const std::string& rssVersion)
|
||||
{
|
||||
//
|
||||
sdv::installation::SPackageVersion sVersion{};
|
||||
size_t nPos = rssVersion.find('.');
|
||||
sVersion.uiMajor = static_cast<uint32_t>(std::atoi(rssVersion.substr(0, nPos).c_str()));
|
||||
size_t nStart = nPos;
|
||||
if (nStart != std::string::npos)
|
||||
{
|
||||
nStart++;
|
||||
nPos = rssVersion.find('.', nPos + 1);
|
||||
sVersion.uiMinor = static_cast<uint32_t>(std::atoi(rssVersion.substr(nStart, nPos).c_str()));
|
||||
}
|
||||
nStart = nPos;
|
||||
if (nStart != std::string::npos)
|
||||
{
|
||||
nStart++;
|
||||
sVersion.uiPatch = static_cast<uint32_t>(std::atoi(rssVersion.substr(nStart).c_str()));
|
||||
}
|
||||
return sVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equality operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator==(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor == sSecond.uiMajor && sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch == sSecond.uiPatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equal or larger than operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator>=(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor > sSecond.uiMajor
|
||||
|| (sFirst.uiMajor == sSecond.uiMajor
|
||||
&& (sFirst.uiMinor > sSecond.uiMinor ||
|
||||
(sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch >= sSecond.uiPatch)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Larger than operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator>(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor > sSecond.uiMajor
|
||||
|| (sFirst.uiMajor == sSecond.uiMajor
|
||||
&& (sFirst.uiMinor > sSecond.uiMinor || (sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch > sSecond.uiPatch)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inequality operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator!=(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor != sSecond.uiMajor || sFirst.uiMinor != sSecond.uiMinor || sFirst.uiPatch != sSecond.uiPatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equality operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator<(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor < sSecond.uiMajor
|
||||
|| (sFirst.uiMajor == sSecond.uiMajor
|
||||
&& (sFirst.uiMinor < sSecond.uiMinor || (sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch < sSecond.uiPatch)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Equality operator for package version.
|
||||
* @param[in] sFirst First package version used for the comparison.
|
||||
* @param[in] sSecond Second package version used for the comparison.
|
||||
* @return The result of the comparison.
|
||||
*/
|
||||
constexpr inline bool operator<=(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
|
||||
{
|
||||
return sFirst.uiMajor < sSecond.uiMajor
|
||||
|| (sFirst.uiMajor == sSecond.uiMajor
|
||||
&& (sFirst.uiMinor < sSecond.uiMinor || (sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch <= sSecond.uiPatch)));
|
||||
}
|
||||
|
||||
#endif // !defined INSTALL_MANIFEST_H
|
||||
52
sdv_services/core/iso_monitor.cpp
Normal file
52
sdv_services/core/iso_monitor.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "iso_monitor.h"
|
||||
#include "sdv_core.h"
|
||||
#include "app_control.h"
|
||||
|
||||
CIsoMonitor::CIsoMonitor(sdv::IInterfaceAccess* pObject) :
|
||||
m_ptrObject(pObject), m_pObjectControl(m_ptrObject.GetInterface<sdv::IObjectControl>())
|
||||
{}
|
||||
|
||||
CIsoMonitor::~CIsoMonitor()
|
||||
{
|
||||
GetAppControl().RequestShutdown();
|
||||
}
|
||||
|
||||
void CIsoMonitor::Initialize(/*in*/ const sdv::u8string& ssObjectConfig)
|
||||
{
|
||||
if (m_pObjectControl)
|
||||
{
|
||||
m_pObjectControl->Initialize(ssObjectConfig);
|
||||
m_eObjectStatus = m_pObjectControl->GetStatus();
|
||||
}
|
||||
else
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CIsoMonitor::GetStatus() const
|
||||
{
|
||||
if (m_pObjectControl) return m_pObjectControl->GetStatus();
|
||||
return m_eObjectStatus;
|
||||
}
|
||||
|
||||
void CIsoMonitor::SetOperationMode(/*in*/ sdv::EOperationMode eMode)
|
||||
{
|
||||
if (m_pObjectControl) m_pObjectControl->SetOperationMode(eMode);
|
||||
}
|
||||
|
||||
void CIsoMonitor::Shutdown()
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
if (m_pObjectControl)
|
||||
{
|
||||
m_pObjectControl->Shutdown();
|
||||
m_eObjectStatus = m_pObjectControl->GetStatus();
|
||||
}
|
||||
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
|
||||
GetAppControl().RequestShutdown();
|
||||
m_pObjectControl = nullptr;
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CIsoMonitor::GetContainedObject()
|
||||
{
|
||||
return m_ptrObject;
|
||||
}
|
||||
69
sdv_services/core/iso_monitor.h
Normal file
69
sdv_services/core/iso_monitor.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef ISOLATION_OBJECT_MONITOR_H
|
||||
#define ISOLATION_OBJECT_MONITOR_H
|
||||
|
||||
#include <support/component_impl.h>
|
||||
|
||||
/**
|
||||
* @brief Isolation object monitor. If shutdown is called, the application leaves the running loop.
|
||||
* @remarks Only valid when running in an isolated application.
|
||||
*/
|
||||
class CIsoMonitor : public sdv::IInterfaceAccess, public sdv::IObjectControl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] pObject Pointer to the object to monitor
|
||||
*/
|
||||
CIsoMonitor(sdv::IInterfaceAccess* pObject);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CIsoMonitor();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_ptrObject)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
virtual void Initialize(/*in*/ const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
virtual sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
virtual void SetOperationMode(/*in*/ sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
* @attention Implement calls to other SDV objects here as this is no longer considered safe during the destructor of the object!
|
||||
* After a call to shutdown any threads/callbacks/etc that could call other SDV objects need to have been stopped.
|
||||
* The SDV object itself is to remain in a state where it can respond to calls to its interfaces as other objects may still call it during the shutdown sequence!
|
||||
* Any subsequent call to GetStatus should return EObjectStatus::destruction_pending
|
||||
*/
|
||||
virtual void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Get the contained object from the isolation monitor.
|
||||
* @return The contained object.
|
||||
*/
|
||||
sdv::IInterfaceAccess* GetContainedObject();
|
||||
|
||||
private:
|
||||
sdv::TInterfaceAccessPtr m_ptrObject; ///< Smart pointer to the object.
|
||||
sdv::IObjectControl* m_pObjectControl = nullptr; ///< Pointer to the object control of the application
|
||||
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status (in case there is no object control).
|
||||
};
|
||||
|
||||
#endif // !defined ISOLATION_OBJECT_MONITOR_H
|
||||
240
sdv_services/core/local_shutdown_request.h
Normal file
240
sdv_services/core/local_shutdown_request.h
Normal file
@@ -0,0 +1,240 @@
|
||||
#ifndef LOCAL_SHUTDOWN_REQUEST_H
|
||||
#define LOCAL_SHUTDOWN_REQUEST_H
|
||||
|
||||
#ifdef _WIN32
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
#elif defined __unix__
|
||||
#include <semaphore.h>
|
||||
#include <time.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
|
||||
/**
|
||||
* @brief Send a exit loop request using a local signaling solution (used when running as essential or standalone application).
|
||||
* @attention There is no feedback that the shutdown was successful!
|
||||
* @param[in] uiInstanceID The instance to shut down.
|
||||
* @return Returns 'true' when the signal could be triggered, 'false' when no signal exists.
|
||||
*/
|
||||
inline bool RequestShutdown(uint32_t uiInstanceID = 1000u)
|
||||
{
|
||||
std::string m_ssSignalName = "SDV_EXIT_LOOP_REQUEST_" + std::to_string(uiInstanceID);
|
||||
#ifdef _WIN32
|
||||
HANDLE hEvent = OpenEventA(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, m_ssSignalName.c_str());
|
||||
if (hEvent && hEvent != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
SetEvent(hEvent);
|
||||
CloseHandle(hEvent);
|
||||
return true;
|
||||
}
|
||||
#elif defined __unix__
|
||||
sem_t* pSemaphore = sem_open(m_ssSignalName.c_str(), 0);
|
||||
if (pSemaphore && pSemaphore != SEM_FAILED)
|
||||
{
|
||||
// Since detecting whether or not the semaphore is triggered can only happen in the wait function, trigger 5 times with
|
||||
// each 1 ms apart.
|
||||
for (uint32_t uiIndex = 0; uiIndex < 5; uiIndex++)
|
||||
{
|
||||
sem_post(pSemaphore);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
sem_close(pSemaphore);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Obviously there is no signal available... no standalone instance is running.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize the signal to receive a shutdown request and check whether a request was fired.
|
||||
*/
|
||||
class CShutdownRequestListener
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CShutdownRequestListener() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructor.
|
||||
* @param[in] uiInstanceID The instance ID to check for.
|
||||
*/
|
||||
CShutdownRequestListener(uint32_t uiInstanceID);
|
||||
|
||||
/**
|
||||
* @brief No support for copy constructor.
|
||||
* @param[in] rListener Reference to the listener to copy.
|
||||
*/
|
||||
CShutdownRequestListener([[maybe_unused]] const CShutdownRequestListener& rListener) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
* @param[in] rListener Reference to the listener to move.
|
||||
*/
|
||||
CShutdownRequestListener(CShutdownRequestListener&& rListener) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CShutdownRequestListener();
|
||||
|
||||
/**
|
||||
* @brief Assignment operator is not supported.
|
||||
* @param[in] rListener Reference to the listener to copy.
|
||||
* @return Reference to the listener class.
|
||||
*/
|
||||
CShutdownRequestListener& operator=([[maybe_unused]] const CShutdownRequestListener& rListener) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move operator.
|
||||
* @param[in] rListener Reference to the listener to move.
|
||||
* @return Reference to the listener class.
|
||||
*/
|
||||
CShutdownRequestListener& operator=(CShutdownRequestListener&& rListener) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Check for validity.
|
||||
* @return Returns whether the signal was initialized correctly.
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* @brief Check whether a trigger occurred. To be called cyclicly.
|
||||
* @param[in] uiWaitForMs The amount of ms to wait for the shutdown request.
|
||||
* @return Returns 'true' when triggered; 'false' when not.
|
||||
*/
|
||||
bool HasTriggered(uint32_t uiWaitForMs) const;
|
||||
|
||||
private:
|
||||
std::string m_ssSignalName; ///< The signal name used for the synchronization.
|
||||
#ifdef _WIN32
|
||||
HANDLE m_hEvent = INVALID_HANDLE_VALUE; ///< The event handle
|
||||
#elif defined __unix__
|
||||
sem_t* m_pSemaphore = nullptr; ///< The semaphore
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
inline CShutdownRequestListener::CShutdownRequestListener(uint32_t uiInstanceID) :
|
||||
m_ssSignalName("SDV_EXIT_LOOP_REQUEST_" + std::to_string(uiInstanceID))
|
||||
{
|
||||
// Prevent multiple instances by opening the named event first (this should fail)...
|
||||
HANDLE hEvent = OpenEventA(SYNCHRONIZE, FALSE, m_ssSignalName.c_str());
|
||||
if (hEvent)
|
||||
CloseHandle(hEvent); // Another instance is already running. Do not create a new event.
|
||||
else
|
||||
m_hEvent = CreateEventA(nullptr, TRUE, FALSE, m_ssSignalName.c_str());
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener::CShutdownRequestListener(CShutdownRequestListener&& rListener) noexcept :
|
||||
m_ssSignalName(std::move(rListener.m_ssSignalName)), m_hEvent(rListener.m_hEvent)
|
||||
{
|
||||
rListener.m_ssSignalName.clear();
|
||||
rListener.m_hEvent = 0;
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener::~CShutdownRequestListener()
|
||||
{
|
||||
// Free the event object
|
||||
if (m_hEvent && m_hEvent != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(m_hEvent);
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener& CShutdownRequestListener::operator=(CShutdownRequestListener&& rListener) noexcept
|
||||
{
|
||||
m_ssSignalName = std::move(rListener.m_ssSignalName);
|
||||
m_hEvent = rListener.m_hEvent;
|
||||
rListener.m_ssSignalName.clear();
|
||||
rListener.m_hEvent = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool CShutdownRequestListener::IsValid() const
|
||||
{
|
||||
return m_hEvent && m_hEvent != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
inline bool CShutdownRequestListener::HasTriggered(uint32_t uiWaitForMs) const
|
||||
{
|
||||
return m_hEvent && m_hEvent != INVALID_HANDLE_VALUE && WaitForSingleObject(m_hEvent, uiWaitForMs) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
#elif defined __unix__
|
||||
|
||||
inline CShutdownRequestListener::CShutdownRequestListener(uint32_t uiInstanceID) :
|
||||
m_ssSignalName("SDV_EXIT_LOOP_REQUEST_" + std::to_string(uiInstanceID))
|
||||
{
|
||||
// Create semaphore object
|
||||
sem_unlink(m_ssSignalName.c_str());
|
||||
m_pSemaphore = sem_open(m_ssSignalName.c_str(), O_CREAT | O_EXCL, 0777 /*O_RDWR*/, 0);
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener::CShutdownRequestListener(CShutdownRequestListener&& rListener) noexcept :
|
||||
m_ssSignalName(std::move(rListener.m_ssSignalName)), m_pSemaphore(rListener.m_pSemaphore)
|
||||
{
|
||||
rListener.m_ssSignalName.clear();
|
||||
rListener.m_pSemaphore = 0;
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener::~CShutdownRequestListener()
|
||||
{
|
||||
if (m_pSemaphore && m_pSemaphore != SEM_FAILED)
|
||||
sem_unlink(m_ssSignalName.c_str());
|
||||
}
|
||||
|
||||
inline CShutdownRequestListener& CShutdownRequestListener::operator=(CShutdownRequestListener&& rListener) noexcept
|
||||
{
|
||||
m_ssSignalName = std::move(rListener.m_ssSignalName);
|
||||
m_pSemaphore = rListener.m_pSemaphore;
|
||||
rListener.m_ssSignalName.clear();
|
||||
rListener.m_pSemaphore = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool CShutdownRequestListener::IsValid() const
|
||||
{
|
||||
return m_pSemaphore && m_pSemaphore != SEM_FAILED;
|
||||
}
|
||||
|
||||
inline bool CShutdownRequestListener::HasTriggered(uint32_t uiWaitForMs) const
|
||||
{
|
||||
// Get the time from the realtime clock
|
||||
timespec sTimespec{};
|
||||
if (clock_gettime(CLOCK_REALTIME, &sTimespec) == -1)
|
||||
return false;
|
||||
uint64_t uiTimeNs = sTimespec.tv_nsec + uiWaitForMs * 1000000ull;
|
||||
sTimespec.tv_nsec = uiTimeNs % 1000000000ull;
|
||||
sTimespec.tv_sec += uiTimeNs / 1000000000ull;
|
||||
|
||||
// Wait for the semaphore
|
||||
return m_pSemaphore && m_pSemaphore != SEM_FAILED && sem_timedwait(m_pSemaphore, &sTimespec) == 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif // !defined LOCAL_SHUTDOWN_REQUEST_H
|
||||
71
sdv_services/core/log_csv_writer.cpp
Normal file
71
sdv_services/core/log_csv_writer.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "log_csv_writer.h"
|
||||
#include <sstream>
|
||||
|
||||
CLogCSVWriter::CLogCSVWriter(std::shared_ptr<std::ostream> ostream, const char separator /*= ';'*/)
|
||||
: m_streamOutput(std::move(ostream))
|
||||
, m_cSeparator(separator)
|
||||
{
|
||||
if ((m_cSeparator <= 0x08) || (m_cSeparator >= 0x0A && m_cSeparator <= 0x1F)
|
||||
|| (m_cSeparator >= 0x30 && m_cSeparator <= 0x39) || (m_cSeparator >= 0x41 && m_cSeparator <= 0x5A)
|
||||
|| (m_cSeparator >= 0x61 && m_cSeparator <= 0x7A) || static_cast<uint8_t>(m_cSeparator) >= 0x7F
|
||||
|| m_cSeparator == '\"' || m_cSeparator == '.' || m_cSeparator == ':' || m_cSeparator == ' ')
|
||||
{
|
||||
throw SForbiddenSeparatorException((std::string("Forbidden Separator '") + m_cSeparator + "'").c_str());
|
||||
}
|
||||
|
||||
std::string intro = std::string("sep=") + m_cSeparator + "\nTimestamp" + m_cSeparator + "Severity" + m_cSeparator
|
||||
+ "Tag" + m_cSeparator + "Message\n";
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxOutput);
|
||||
*m_streamOutput << intro;
|
||||
m_streamOutput->flush();
|
||||
}
|
||||
|
||||
void CLogCSVWriter::Write(const std::string& message, const sdv::core::ELogSeverity severity,
|
||||
const std::chrono::time_point<std::chrono::system_clock> timestamp)
|
||||
{
|
||||
std::string output = GetDateTime(timestamp) + m_cSeparator + GetSeverityString(severity) +
|
||||
m_cSeparator + "\"" + m_cSeparator + "\"" + Escape(message) + "\"\n";
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxOutput);
|
||||
*m_streamOutput << output;
|
||||
m_streamOutput->flush();
|
||||
}
|
||||
|
||||
std::string CLogCSVWriter::GetDateTime(const std::chrono::time_point<std::chrono::system_clock> timestamp)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
std::stringstream output;
|
||||
microseconds micros = duration_cast<microseconds>(timestamp.time_since_epoch());
|
||||
uint32_t fractionalSeconds = micros.count() % 1000000;
|
||||
std::string timeFormat = "%Y.%m.%d %H:%M:%S.";
|
||||
std::time_t time = system_clock::to_time_t(timestamp);
|
||||
output << std::put_time(std::localtime(&time), timeFormat.c_str());
|
||||
output << std::setw(6) << std::setfill('0') << fractionalSeconds;
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::string CLogCSVWriter::GetSeverityString(sdv::core::ELogSeverity severity)
|
||||
{
|
||||
switch (severity)
|
||||
{
|
||||
case sdv::core::ELogSeverity::trace: return "Trace";
|
||||
case sdv::core::ELogSeverity::debug: return "Debug";
|
||||
case sdv::core::ELogSeverity::info: return "Info";
|
||||
case sdv::core::ELogSeverity::warning: return "Warning";
|
||||
case sdv::core::ELogSeverity::error: return "Error";
|
||||
case sdv::core::ELogSeverity::fatal: return "Fatal";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string CLogCSVWriter::Escape(const std::string& toEscape)
|
||||
{
|
||||
std::string escaped = toEscape;
|
||||
const std::string with("\"\"");
|
||||
for (std::string::size_type pos{}; std::string::npos != (pos = escaped.find('\"', pos)); pos += with.length())
|
||||
{
|
||||
escaped.replace(pos, 1, with.data(), with.length());
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
68
sdv_services/core/log_csv_writer.h
Normal file
68
sdv_services/core/log_csv_writer.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef LOG_CSV_WRITER_H
|
||||
#define LOG_CSV_WRITER_H
|
||||
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <interfaces/log.h>
|
||||
#include <mutex>
|
||||
|
||||
/**
|
||||
* @brief Writes CSV entries to a given stream.
|
||||
*/
|
||||
class CLogCSVWriter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a CSV writer.
|
||||
* @param[in] ostream stream the CSV writer will write to.
|
||||
* @param[in] separator sets the separator of the CSV file, default is ';'.
|
||||
*/
|
||||
CLogCSVWriter(std::shared_ptr<std::ostream> ostream, const char separator = ';');
|
||||
|
||||
/**
|
||||
* @brief Creates a log entry in the log.
|
||||
* @param[in] message message of the log entry.
|
||||
* @param[in] severity severity of the log entry.
|
||||
* @param[in] timestamp timestamp of the log entry.
|
||||
*/
|
||||
void Write(const std::string& message, const sdv::core::ELogSeverity severity,
|
||||
const std::chrono::time_point<std::chrono::system_clock> timestamp);
|
||||
|
||||
/**
|
||||
* @brief Creates a string of the given timestamp including nano seconds.
|
||||
* @param[in] timestamp Timestamp to be converted to a string.
|
||||
* @return Returns the string representing the timestamp including nano seconds.
|
||||
*/
|
||||
std::string GetDateTime(const std::chrono::time_point<std::chrono::system_clock> timestamp);
|
||||
|
||||
/**
|
||||
* @brief Creates string from the given severity.
|
||||
* @param[in] severity Severity to be converted to a string.
|
||||
* @return Returns the string representing the severity.
|
||||
*/
|
||||
static std::string GetSeverityString(sdv::core::ELogSeverity severity);
|
||||
|
||||
/**
|
||||
* @brief Exception if an invalid separator is set.
|
||||
*/
|
||||
struct SForbiddenSeparatorException : public std::runtime_error
|
||||
{
|
||||
/**
|
||||
* @brief Construct a forbidden separator Exception object.
|
||||
* @param[in] message The reason for throwing this exception.
|
||||
*/
|
||||
SForbiddenSeparatorException(const char* message)
|
||||
: std::runtime_error(message)
|
||||
{}
|
||||
};
|
||||
|
||||
private:
|
||||
static std::string Escape(const std::string& toEscape);
|
||||
|
||||
std::shared_ptr<std::ostream> m_streamOutput; ///< Output stream
|
||||
const char m_cSeparator; ///< The separator character
|
||||
mutable std::mutex m_mtxOutput; ///< Protect stream access
|
||||
};
|
||||
|
||||
#endif // !defined LOG_CSV_WRITER_H
|
||||
151
sdv_services/core/logger.cpp
Normal file
151
sdv_services/core/logger.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
#include "logger.h"
|
||||
#include <sstream>
|
||||
#include "sdv_core.h"
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
|
||||
#ifdef __unix__
|
||||
#include <syslog.h>
|
||||
#endif
|
||||
#ifdef __GNUC__
|
||||
#include <unistd.h> // for getpid
|
||||
#endif
|
||||
|
||||
CLogger::~CLogger()
|
||||
{
|
||||
#ifdef __unix__
|
||||
if (m_bLogOpen) closelog();
|
||||
#endif
|
||||
}
|
||||
|
||||
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 (tProcessID) std::clog << "[PID#" << static_cast<int64_t>(tProcessID) << "] ";
|
||||
if (!ssObjectName.empty()) std::clog << ssObjectName << " ";
|
||||
switch (eSeverity)
|
||||
{
|
||||
case sdv::core::ELogSeverity::debug:
|
||||
std::clog << "Debug: " << ssMessage << std::endl;
|
||||
break;
|
||||
case sdv::core::ELogSeverity::trace:
|
||||
std::clog << "Trace: " << ssMessage << std::endl;
|
||||
break;
|
||||
case sdv::core::ELogSeverity::info:
|
||||
std::clog << "Info: " << ssMessage << std::endl;
|
||||
break;
|
||||
case sdv::core::ELogSeverity::warning:
|
||||
std::clog << "Warning: " << ssMessage << std::endl;
|
||||
break;
|
||||
case sdv::core::ELogSeverity::error:
|
||||
std::clog << "Error: " << ssMessage << std::endl;
|
||||
break;
|
||||
case sdv::core::ELogSeverity::fatal:
|
||||
std::clog << "Fatal: " << ssMessage << std::endl;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the log filter
|
||||
if (static_cast<uint32_t>(eSeverity) < static_cast<uint32_t>(m_eFilter)) return;
|
||||
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxLogger);
|
||||
if (m_ssProgramtag.empty())
|
||||
m_ssProgramtag = std::string("SDV_LOG_") + std::to_string(getpid());
|
||||
|
||||
sdv::u8string ssExtendedMessage;
|
||||
ssExtendedMessage += ssSrcFile + "(" + std::to_string(uiSrcLine) + "): " + ssMessage;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!m_ptrWriter)
|
||||
{
|
||||
std::string logFileName = m_ssProgramtag + "_" +
|
||||
GetDateTime(std::chrono::system_clock::now()) + ".log";
|
||||
|
||||
auto path = GetExecDirectory() / logFileName;
|
||||
auto stream = std::make_shared<std::ofstream>(path, std::ios_base::out | std::ios_base::app);
|
||||
if (!stream->is_open())
|
||||
{
|
||||
std::cerr << "ERROR: Log file cannot be created!" << std::endl;
|
||||
return;
|
||||
}
|
||||
m_ptrWriter = std::move(std::make_unique<CLogCSVWriter>(stream));
|
||||
if (!m_ptrWriter)
|
||||
{
|
||||
std::cerr << "ERROR: Cannot create CSV write object!" << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_ptrWriter->Write(ssExtendedMessage, eSeverity, std::chrono::system_clock::now());
|
||||
#elif defined __unix__
|
||||
if (!m_bLogOpen)
|
||||
openlog(m_ssProgramtag.c_str(), LOG_PID | LOG_CONS, LOG_USER);
|
||||
m_bLogOpen = true;
|
||||
switch(eSeverity)
|
||||
{
|
||||
case sdv::core::ELogSeverity::trace : syslog(LOG_NOTICE, "%s", ssExtendedMessage.c_str()); break;
|
||||
case sdv::core::ELogSeverity::debug : syslog(LOG_DEBUG, "%s", ssExtendedMessage.c_str()); break;
|
||||
case sdv::core::ELogSeverity::info : syslog(LOG_INFO, "%s", ssExtendedMessage.c_str()); break;
|
||||
case sdv::core::ELogSeverity::warning : syslog(LOG_WARNING, "%s", ssExtendedMessage.c_str()); break;
|
||||
case sdv::core::ELogSeverity::error : syslog(LOG_ERR, "%s", ssExtendedMessage.c_str()); break;
|
||||
case sdv::core::ELogSeverity::fatal : syslog(LOG_CRIT, "%s", ssExtendedMessage.c_str()); break;
|
||||
default: std::cerr << "ERROR: Logging level is undefined!"; break;
|
||||
}
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
}
|
||||
|
||||
void CLogger::SetProgramTag(const sdv::u8string& ssTag)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxLogger);
|
||||
|
||||
if (m_ssProgramtag.empty())
|
||||
m_ssProgramtag = ssTag;
|
||||
}
|
||||
|
||||
sdv::u8string CLogger::GetProgramTag() const
|
||||
{
|
||||
return m_ssProgramtag;
|
||||
}
|
||||
|
||||
void CLogger::SetLogFilter(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ sdv::core::ELogSeverity eViewSeverity)
|
||||
{
|
||||
m_eFilter = eSeverity;
|
||||
m_eViewFilter = eViewSeverity;
|
||||
}
|
||||
|
||||
sdv::core::ELogSeverity CLogger::GetLogFilter() const
|
||||
{
|
||||
return m_eFilter;
|
||||
}
|
||||
|
||||
sdv::core::ELogSeverity CLogger::GetViewFilter() const
|
||||
{
|
||||
return m_eViewFilter;
|
||||
}
|
||||
|
||||
std::string CLogger::GetDateTime(std::chrono::time_point<std::chrono::system_clock> timestamp)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
std::stringstream output;
|
||||
microseconds micros = duration_cast<microseconds>(timestamp.time_since_epoch());
|
||||
|
||||
std::string timeFormat = "_%Y.%m.%d_%H-%M-%S";
|
||||
|
||||
std::time_t time = system_clock::to_time_t(timestamp);
|
||||
output << std::put_time(std::localtime(&time), timeFormat.c_str());
|
||||
return output.str();
|
||||
}
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
CLogger& CLoggerService::GetDefaultLogger()
|
||||
{
|
||||
return ::GetDefaultLogger();
|
||||
}
|
||||
|
||||
#endif
|
||||
128
sdv_services/core/logger.h
Normal file
128
sdv_services/core/logger.h
Normal file
@@ -0,0 +1,128 @@
|
||||
#ifndef LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include <interfaces/log.h>
|
||||
#include <support/interface_ptr.h>
|
||||
#include <support/component_impl.h>
|
||||
#include "log_csv_writer.h"
|
||||
|
||||
/**
|
||||
* @brief Logger class implementation for Windows to enable logging in a logfile.
|
||||
*/
|
||||
class CLogger : public sdv::IInterfaceAccess, public sdv::core::ILogger, public sdv::core::ILoggerConfig
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CLogger() = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CLogger();
|
||||
|
||||
// Interface table
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ILogger)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ILoggerConfig)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Log a message to the SDV log. Overload of sdv::core::ILogger::Log.
|
||||
* @param[in] eSeverity Severity level of the log message which will be logged, e.g. info, warning, error etc.
|
||||
* @param[in] ssSrcFile The source file that caused the log entry.
|
||||
* @param[in] uiSrcLine The line number in the source file that caused the log entry.
|
||||
* @param[in] tProcessID Process ID of the process reporting this log entry.
|
||||
* @param[in] ssObjectName Name of the object if the log entry is supplied by a component.
|
||||
* @param[in] ssMessage The log message that will be logged.
|
||||
*/
|
||||
virtual void Log(/*in*/ 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) override;
|
||||
|
||||
/**
|
||||
* @brief Initialize the logging from SDV platform abstraction. Overload of sdv::core::ILoggerConfig::SetProgramTag.
|
||||
* @details This function needs to be called before starting to log.
|
||||
* @param[in] ssTag Provided tag to create log.
|
||||
*/
|
||||
virtual void SetProgramTag(/*in*/ const sdv::u8string& ssTag) override;
|
||||
|
||||
/**
|
||||
* @brief Get the program tag used for logging. Overload of sdv::core::ILoggerConfig::GetProgramTag.
|
||||
* @return The string containing the program tag.
|
||||
*/
|
||||
virtual sdv::u8string GetProgramTag() const override;
|
||||
|
||||
/**
|
||||
* @brief Filter the log messages based on severity. Overload of sdv::core::ILoggerConfig::SetLogFilter.
|
||||
* @param[in] eSeverity The severity level to use as a lowest level for logging. Default is "info" meaning, that
|
||||
* debug and trace messages will not be logged.
|
||||
* @param[in] eViewSeverity The severity level to use as a lowest level for viewing. Default is "error" meaning, that
|
||||
* debug, trace, info, warning and error messages will not be shown in console output.
|
||||
*/
|
||||
virtual void SetLogFilter(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ sdv::core::ELogSeverity eViewSeverity) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current log severity filter level. Overload of sdv::core::ILoggerConfig::GetLogFilter.
|
||||
* @return The severity level of the log filter.
|
||||
*/
|
||||
virtual sdv::core::ELogSeverity GetLogFilter() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the current view severity level. Overload of sdv::core::ILoggerConfig::GetViewFilter.
|
||||
* @return The severity level of the view filter.
|
||||
*/
|
||||
virtual sdv::core::ELogSeverity GetViewFilter() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Creates a string of the given timestamp without nano seconds.
|
||||
* @param[in] timestamp Timestamp to be converted to a string.
|
||||
* @return Returns the string representing the timestamp without nano seconds.
|
||||
*/
|
||||
static std::string GetDateTime(std::chrono::time_point<std::chrono::system_clock> timestamp);
|
||||
|
||||
std::recursive_mutex m_mtxLogger; ///< Mutex for logging
|
||||
sdv::core::ELogSeverity m_eFilter = sdv::core::ELogSeverity::info; ///< Severity filter for logging
|
||||
sdv::core::ELogSeverity m_eViewFilter = sdv::core::ELogSeverity::info; ///< Severity filter for viewing
|
||||
std::string m_ssProgramtag; ///< Program tag to use for logging.
|
||||
#ifdef _WIN32
|
||||
std::unique_ptr<CLogCSVWriter> m_ptrWriter; ///< Pointer to CSVWriter
|
||||
#elif defined __unix__
|
||||
bool m_bLogOpen = false; ///< Check if the logger is opened.
|
||||
#else
|
||||
#error OS currently not supported!
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
/**
|
||||
* @brief Logger service
|
||||
*/
|
||||
class CLoggerService : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
CLoggerService() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(GetDefaultLogger())
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("DefaultLoggerService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Get access to the repository.
|
||||
* @return Returns a reference to the one repository of this module.
|
||||
*/
|
||||
static CLogger& GetDefaultLogger();
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CLoggerService)
|
||||
#endif
|
||||
|
||||
#endif // !defined LOGGER_H
|
||||
77
sdv_services/core/logger_control.cpp
Normal file
77
sdv_services/core/logger_control.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "logger_control.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <process.h> // Needed for _getpid
|
||||
#endif
|
||||
|
||||
CLoggerControl::~CLoggerControl()
|
||||
{
|
||||
// Reset the logger. This will finialize an open log.
|
||||
SetLogger(nullptr);
|
||||
}
|
||||
|
||||
void CLoggerControl::Log(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
|
||||
/*in*/ const sdv::process::TProcessID tProcessID, /*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssMessage)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxPrematureLog);
|
||||
|
||||
// Is a registered logger available?
|
||||
if (m_pLogger)
|
||||
{
|
||||
sdv::core::ILogger* pLogger = m_pLogger;
|
||||
lock.unlock();
|
||||
pLogger->Log(eSeverity, ssSrcFile, uiSrcLine, tProcessID, ssObjectName, ssMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the log entry for later use. Store at the most 1000 messages.
|
||||
if (m_queuePrematureLog.size() >= 1000) return;
|
||||
if (m_queuePrematureLog.size() == 999)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
sdv::process::TProcessID tProcessIDLocal = _getpid();
|
||||
#elif defined __unix__
|
||||
sdv::process::TProcessID tProcessIDLocal = getpid();
|
||||
#else
|
||||
#error The OS currently is not supported!
|
||||
#endif
|
||||
SLogEntry entryTruncated{ sdv::core::ELogSeverity::warning, "", 0, tProcessIDLocal, "", "Log truncated..." };
|
||||
m_queuePrematureLog.push(entryTruncated);
|
||||
return;
|
||||
}
|
||||
SLogEntry entry{ eSeverity, ssSrcFile, uiSrcLine, tProcessID, ssObjectName, ssMessage };
|
||||
m_queuePrematureLog.push(entry);
|
||||
}
|
||||
|
||||
void CLoggerControl::SetLogger(sdv::core::ILogger* pLogger)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxPrematureLog);
|
||||
if (pLogger == m_pLogger) return;
|
||||
|
||||
// When assigning a logger, write the stored entries to the log
|
||||
#ifdef _WIN32
|
||||
sdv::process::TProcessID tProcessID = _getpid();
|
||||
#elif defined __unix__
|
||||
sdv::process::TProcessID tProcessID = getpid();
|
||||
#else
|
||||
#error The OS currently is not supported!
|
||||
#endif
|
||||
if (pLogger)
|
||||
{
|
||||
pLogger->Log(sdv::core::ELogSeverity::info, "", 0, tProcessID, "", "Begin logging...");
|
||||
while (!m_queuePrematureLog.empty())
|
||||
{
|
||||
SLogEntry& rentry = m_queuePrematureLog.front();
|
||||
pLogger->Log(rentry.eSeverity, rentry.ssSrcFile, rentry.uiSrcLine, rentry.tProcessID, rentry.ssObjectName,
|
||||
rentry.ssMessage);
|
||||
m_queuePrematureLog.pop();
|
||||
}
|
||||
}
|
||||
else // Finish the log
|
||||
{
|
||||
m_pLogger->Log(sdv::core::ELogSeverity::info, "", 0, tProcessID, "", "End logging...");
|
||||
}
|
||||
|
||||
// Store the new logger pointer
|
||||
m_pLogger = pLogger;
|
||||
}
|
||||
69
sdv_services/core/logger_control.h
Normal file
69
sdv_services/core/logger_control.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef LOGGER_CONTROL_H
|
||||
#define LOGGER_CONTROL_H
|
||||
|
||||
#include <support/interface_ptr.h>
|
||||
#include <interfaces/log.h>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* @brief Logger control class.
|
||||
*/
|
||||
class CLoggerControl : public sdv::IInterfaceAccess, public sdv::core::ILogger
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CLoggerControl() = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CLoggerControl();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ILogger)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Log a message to the SDV log. Overload of sdv::core::ILogger::Log.
|
||||
* @param[in] eSeverity Severity level of the log message which will be logged, e.g. info, warning, error etc.
|
||||
* @param[in] ssSrcFile The source file that caused the log entry.
|
||||
* @param[in] uiSrcLine The line number in the source file that caused the log entry.
|
||||
* @param[in] tProcessID Process ID of the process reporting this log entry.
|
||||
* @param[in] ssObjectName Name of the object if the log entry is supplied by a component.
|
||||
* @param[in] ssMessage The log message that will be logged.
|
||||
*/
|
||||
virtual void Log(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
|
||||
/*in*/ const sdv::process::TProcessID tProcessID, /*in*/ const sdv::u8string& ssObjectName,
|
||||
/*in*/ const sdv::u8string& ssMessage) override;
|
||||
|
||||
/**
|
||||
* @brief Set the logger to use for logging.
|
||||
* @param[in] pLogger
|
||||
*/
|
||||
void SetLogger(sdv::core::ILogger* pLogger);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Log entry to store.
|
||||
*/
|
||||
struct SLogEntry
|
||||
{
|
||||
sdv::core::ELogSeverity eSeverity; ///< Severity level of the log message.
|
||||
sdv::u8string ssSrcFile; ///< The source file that caused the log entry.
|
||||
uint32_t uiSrcLine; ///< The line number in the source file that caused the log entry.
|
||||
sdv::process::TProcessID tProcessID; ///< Process ID of the process submitting the log entry.
|
||||
sdv::u8string ssObjectName; ///< Name of the object if the log entry is submitted by an object.
|
||||
sdv::u8string ssMessage; ///< The log message that will be logged.
|
||||
};
|
||||
|
||||
mutable std::mutex m_mtxPrematureLog; ///< Protect the logger queue access.
|
||||
std::queue<SLogEntry> m_queuePrematureLog; ///< Logger queue to store messages before a logger was assigned.
|
||||
sdv::core::ILogger* m_pLogger = nullptr; ///< Interface for the actual logger.
|
||||
};
|
||||
|
||||
#endif // !defined LOGGER_CONTROL_H
|
||||
62
sdv_services/core/memory.cpp
Normal file
62
sdv_services/core/memory.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "memory.h"
|
||||
|
||||
sdv::pointer<uint8_t> CMemoryManager::Allocate(uint32_t uiLength)
|
||||
{
|
||||
return sdv::internal::make_ptr<uint8_t>(this, uiLength);
|
||||
}
|
||||
|
||||
void* CMemoryManager::Alloc(size_t nSize)
|
||||
{
|
||||
void* pAlloc = malloc(nSize);
|
||||
#ifdef MEMORY_TRACKER
|
||||
std::unique_lock<std::mutex> lock(m_mtxTracker);
|
||||
if (pAlloc)
|
||||
m_mapTracker.insert(std::make_pair(pAlloc, nSize));
|
||||
#endif
|
||||
return pAlloc;
|
||||
}
|
||||
|
||||
void* CMemoryManager::Realloc(void* pData, size_t nSize)
|
||||
{
|
||||
#ifdef MEMORY_TRACKER
|
||||
std::unique_lock<std::mutex> lock(m_mtxTracker);
|
||||
auto itAlloc = m_mapTracker.find(pData);
|
||||
if (pData && itAlloc == m_mapTracker.end())
|
||||
{
|
||||
std::cout << "Illegal request for resizing memory at location 0x" << (void*)pData << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
lock.unlock();
|
||||
#endif
|
||||
|
||||
void* pAlloc = realloc(pData, nSize);
|
||||
|
||||
#ifdef MEMORY_TRACKER
|
||||
if (pAlloc)
|
||||
{
|
||||
lock.lock();
|
||||
m_mapTracker.erase(pData);
|
||||
m_mapTracker.insert(std::make_pair(pAlloc, nSize));
|
||||
}
|
||||
#endif
|
||||
|
||||
return pAlloc;
|
||||
}
|
||||
|
||||
void CMemoryManager::Free(void* pData)
|
||||
{
|
||||
#ifdef MEMORY_TRACKER
|
||||
std::unique_lock<std::mutex> lock(m_mtxTracker);
|
||||
auto itAlloc = m_mapTracker.find(pData);
|
||||
if (itAlloc == m_mapTracker.end())
|
||||
{
|
||||
std::cout << "Illegal request for freeing memory at location 0x" << (void*)pData << std::endl;
|
||||
return;
|
||||
}
|
||||
else
|
||||
m_mapTracker.erase(itAlloc);
|
||||
lock.unlock();
|
||||
#endif
|
||||
|
||||
free(pData);
|
||||
}
|
||||
57
sdv_services/core/memory.h
Normal file
57
sdv_services/core/memory.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef MEMORY_H
|
||||
#define MEMORY_H
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include <support/pointer.h>
|
||||
#include <interfaces/mem.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <support/mem_access.h>
|
||||
|
||||
/**
|
||||
* @brief Memory Manager service
|
||||
*/
|
||||
class CMemoryManager : public sdv::core::IMemoryAlloc, public sdv::IInterfaceAccess, public sdv::internal::IInternalMemAlloc
|
||||
{
|
||||
public:
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IMemoryAlloc)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Allocate a memory block of the provided length. Overload of sdv::core::IMemoryAlloc::Allocate.
|
||||
* @param[in] uiLength The length of the memory block to allocate.
|
||||
* @return Smart pointer to the memory allocation or NULL when allocating was not possible.
|
||||
*/
|
||||
virtual sdv::pointer<uint8_t> Allocate(/*in*/ uint32_t uiLength) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Allocate memory. Overload of internal::IInternalMemAlloc::Alloc.
|
||||
* @param[in] nSize The size of the memory to allocate (in bytes).
|
||||
* @return Pointer to the memory allocation or NULL when memory allocation failed.
|
||||
*/
|
||||
virtual void* Alloc(size_t nSize) override;
|
||||
|
||||
/**
|
||||
* @brief Reallocate memory. Overload of internal::IInternalMemAlloc::Realloc.
|
||||
* @param[in] pData Pointer to a previous allocation or NULL when no previous allocation was available.
|
||||
* @param[in] nSize The size of the memory to allocate (in bytes).
|
||||
* @return Pointer to the memory allocation or NULL when memory allocation failed.
|
||||
*/
|
||||
virtual void* Realloc(void* pData, size_t nSize) override;
|
||||
|
||||
/**
|
||||
* @brief Free a memory allocation. Overload of internal::IInternalMemAlloc::Free.
|
||||
* @param[in] pData Pointer to a previous allocation.
|
||||
*/
|
||||
virtual void Free(void* pData) override;
|
||||
|
||||
#ifdef MEMORY_TRACKER
|
||||
std::mutex m_mtxTracker; ///< Synchronize map access
|
||||
std::map<void*, size_t> m_mapTracker; ///< Memory manager allocation tracker
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // !define MEMORY_H
|
||||
374
sdv_services/core/module.cpp
Normal file
374
sdv_services/core/module.cpp
Normal file
@@ -0,0 +1,374 @@
|
||||
#include "module.h"
|
||||
#include "sdv_core.h"
|
||||
#include <support/local_service_access.h>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef _WIN32
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#pragma push_macro("GetObject")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("interface")
|
||||
#pragma pop_macro("GetObject")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
||||
#endif
|
||||
#endif // defined _WIN32
|
||||
|
||||
#ifdef __unix__
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
CModuleInst::CModuleInst(const std::filesystem::path& rpathModuleConfigPath, const std::filesystem::path& rpathModule) noexcept :
|
||||
m_pathModuleConfigPath(rpathModuleConfigPath)
|
||||
{
|
||||
if (!rpathModule.empty())
|
||||
Load(rpathModule);
|
||||
}
|
||||
|
||||
CModuleInst::~CModuleInst()
|
||||
{
|
||||
Unload(true);
|
||||
}
|
||||
|
||||
bool CModuleInst::IsValid() const noexcept
|
||||
{
|
||||
return m_tModuleID != 0 && m_pFactory;
|
||||
}
|
||||
|
||||
std::string CModuleInst::GetDefaultObjectName(const std::string& ssClassName) const
|
||||
{
|
||||
auto itClass = m_mapClassInfo.find(ssClassName);
|
||||
if (itClass == m_mapClassInfo.end()) return std::string();
|
||||
return itClass->second.ssDefaultObjectName.empty() ? itClass->second.ssClassName : itClass->second.ssDefaultObjectName;
|
||||
}
|
||||
|
||||
bool CModuleInst::IsSingleton(const std::string& ssClassName) const
|
||||
{
|
||||
auto itClass = m_mapClassInfo.find(ssClassName);
|
||||
if (itClass == m_mapClassInfo.end()) return false;
|
||||
return (itClass->second.uiFlags & static_cast<uint32_t>(sdv::EObjectFlags::singleton)) ? true : false;
|
||||
}
|
||||
|
||||
std::vector<std::string> CModuleInst::GetAvailableClasses() const
|
||||
{
|
||||
std::vector<std::string> vecClasses;
|
||||
for (const auto& rvtClass : m_mapClassInfo)
|
||||
vecClasses.push_back(rvtClass.first);
|
||||
return vecClasses;
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CModuleInst::CreateObject(const std::string& ssClassName)
|
||||
{
|
||||
if(!m_pFactory)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return m_pFactory->CreateObject(ssClassName.c_str());
|
||||
}
|
||||
|
||||
void CModuleInst::DestroyObject(sdv::IInterfaceAccess* object)
|
||||
{
|
||||
if(m_pFactory)
|
||||
{
|
||||
m_pFactory->DestroyObject(object);
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path CModuleInst::GetModuleConfigPath() const
|
||||
{
|
||||
return m_pathModuleConfigPath;
|
||||
}
|
||||
|
||||
std::filesystem::path CModuleInst::GetModulePath() const
|
||||
{
|
||||
return m_pathModule;
|
||||
}
|
||||
|
||||
sdv::core::TModuleID CModuleInst::GetModuleID() const
|
||||
{
|
||||
return m_tModuleID;
|
||||
}
|
||||
|
||||
sdv::core::SModuleInfo CModuleInst::GetModuleInfo() const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
|
||||
sdv::core::SModuleInfo sInfo{};
|
||||
sInfo.tModuleID = m_tModuleID;
|
||||
sInfo.ssPath = m_pathModule.filename().generic_u8string();
|
||||
sInfo.ssFilename = m_pathModule.filename().generic_u8string();
|
||||
sInfo.uiVersion = m_uiIfcVersion;
|
||||
sInfo.bActive = m_fnActiveObjects ? m_fnActiveObjects() : false;
|
||||
return sInfo;
|
||||
}
|
||||
|
||||
std::optional<sdv::SClassInfo> CModuleInst::GetClassInfo(const std::string& rssClassName) const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
|
||||
const auto itClass = m_mapClassInfo.find(rssClassName);
|
||||
if (itClass == m_mapClassInfo.end()) return {};
|
||||
std::optional<sdv::SClassInfo> optClassInfo = itClass->second;
|
||||
|
||||
// Add the module path to the result
|
||||
optClassInfo->ssModulePath = m_pathModule.generic_u8string();
|
||||
|
||||
return itClass->second;
|
||||
}
|
||||
|
||||
bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
|
||||
|
||||
// Cannot load again.
|
||||
if (m_tModuleID) return false;
|
||||
m_pathModule = rpathModule;
|
||||
|
||||
// Core services bypass.
|
||||
if (rpathModule == "core_services.sdv")
|
||||
{
|
||||
m_tModuleID = sdv::core::tCoreLibModule;
|
||||
m_fnActiveObjects = &::HasActiveObjects;
|
||||
m_fnGetFactory = &::GetModuleFactory;
|
||||
m_fnGetManifest = &::GetManifest;
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef _WIN32
|
||||
m_tModuleID = reinterpret_cast<uint64_t>(LoadLibrary(rpathModule.native().c_str()));
|
||||
#elif defined __unix__
|
||||
m_tModuleID = reinterpret_cast<uint64_t>(dlopen(rpathModule.native().c_str(), RTLD_LAZY));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
if (!m_tModuleID)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
std::string ssError(1024, '\0');
|
||||
FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
GetLastError(),
|
||||
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
||||
&ssError.front(),
|
||||
static_cast<DWORD>(ssError.size()), NULL);
|
||||
ssError[1023] = '\0';
|
||||
ssError.resize(strlen(ssError.c_str()));
|
||||
#elif defined __unix__
|
||||
std::string ssError = dlerror();
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
SDV_LOG_ERROR("Error opening SDV module: \"", rpathModule.generic_u8string(), "\" error: ", ssError);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check whether the module exposes the necessary functions
|
||||
#ifdef _WIN32
|
||||
m_fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(GetProcAddress(reinterpret_cast<HMODULE>(m_tModuleID), "GetModuleFactory"));
|
||||
m_fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(GetProcAddress(reinterpret_cast<HMODULE>(m_tModuleID), "HasActiveObjects"));
|
||||
m_fnGetManifest = reinterpret_cast<TFNGetManifest*>(GetProcAddress(reinterpret_cast<HMODULE>(m_tModuleID), "GetManifest"));
|
||||
#elif defined __unix__
|
||||
m_fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(dlsym(reinterpret_cast<void*>(m_tModuleID), "GetModuleFactory"));
|
||||
m_fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(dlsym(reinterpret_cast<void*>(m_tModuleID), "HasActiveObjects"));
|
||||
m_fnGetManifest = reinterpret_cast<TFNGetManifest*>(dlsym(reinterpret_cast<void*>(m_tModuleID), "GetManifest"));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
if (!m_fnGetFactory || !m_fnActiveObjects || !m_fnGetManifest || !m_fnGetManifest())
|
||||
{
|
||||
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(),
|
||||
" error: The module doesn't expose access functions!");
|
||||
Unload(true);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the manifest
|
||||
std::string ssManifest = m_fnGetManifest();
|
||||
|
||||
try
|
||||
{
|
||||
CParserTOML parser(ssManifest);
|
||||
|
||||
// Check for the interface version - currently must be equal.
|
||||
auto ptrVersion = parser.GetRoot().GetDirect("Interface.Version");
|
||||
if (ptrVersion) m_uiIfcVersion = ptrVersion->GetValue();
|
||||
if (!ptrVersion || m_uiIfcVersion != SDVFrameworkInterfaceVersion)
|
||||
{
|
||||
// Incompatible interface.
|
||||
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(), " error: incompatible version ", m_uiIfcVersion,
|
||||
" (required ", SDVFrameworkInterfaceVersion, ")");
|
||||
Unload(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a pointer to the factory for the current interface.
|
||||
m_pFactory = sdv::TInterfaceAccessPtr(m_fnGetFactory(SDVFrameworkInterfaceVersion)).GetInterface<sdv::IObjectFactory>();
|
||||
if (!m_pFactory)
|
||||
{
|
||||
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(), " error: no object factory available");
|
||||
Unload(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get available classes
|
||||
auto ptrComponents = parser.GetRoot().GetDirect("Component");
|
||||
if (!ptrComponents || !ptrComponents->GetArray())
|
||||
{
|
||||
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(), " error: no components available");
|
||||
Unload(true);
|
||||
return false;
|
||||
}
|
||||
for (std::uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++)
|
||||
{
|
||||
// Fill in the class info.
|
||||
sdv::SClassInfo sInfo{};
|
||||
auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex);
|
||||
if (!ptrComponent) continue;
|
||||
auto ptrClassName = ptrComponent->GetDirect("Class");
|
||||
if (!ptrClassName) continue;
|
||||
sInfo.ssClassName = static_cast<std::string>(ptrClassName->GetValue());
|
||||
auto ptrAliases = ptrComponent->GetDirect("Aliases");
|
||||
if (ptrAliases)
|
||||
{
|
||||
auto ptrAliasesArray = ptrAliases->GetArray();
|
||||
for (uint32_t uiAliasIndex = 0; ptrAliasesArray && uiAliasIndex < ptrAliasesArray->GetCount(); uiAliasIndex++)
|
||||
{
|
||||
auto ptrClassAlias = ptrAliasesArray->Get(uiAliasIndex);
|
||||
if (ptrClassAlias)
|
||||
sInfo.seqClassAliases.push_back(static_cast<sdv::u8string>(ptrClassAlias->GetValue()));
|
||||
}
|
||||
}
|
||||
auto ptrDefaultName = ptrComponent->GetDirect("DefaultName");
|
||||
if (ptrDefaultName) sInfo.ssDefaultObjectName = static_cast<std::string>(ptrDefaultName->GetValue());
|
||||
else sInfo.ssDefaultObjectName = sInfo.ssClassName;
|
||||
auto ptrType = ptrComponent->GetDirect("Type");
|
||||
if (!ptrType) continue;
|
||||
std::string ssType = static_cast<std::string>(ptrType->GetValue());
|
||||
if (ssType == "System") sInfo.eType = sdv::EObjectType::SystemObject;
|
||||
else if (ssType == "Device") sInfo.eType = sdv::EObjectType::Device;
|
||||
else if (ssType == "BasicService") sInfo.eType = sdv::EObjectType::BasicService;
|
||||
else if (ssType == "ComplexService") sInfo.eType = sdv::EObjectType::ComplexService;
|
||||
else if (ssType == "App") sInfo.eType = sdv::EObjectType::Application;
|
||||
else if (ssType == "Proxy") sInfo.eType = sdv::EObjectType::Proxy;
|
||||
else if (ssType == "Stub") sInfo.eType = sdv::EObjectType::Stub;
|
||||
else if (ssType == "Utility") sInfo.eType = sdv::EObjectType::Utility;
|
||||
else continue;
|
||||
auto ptrSingleton = ptrComponent->GetDirect("Singleton");
|
||||
if (ptrSingleton && static_cast<bool>(ptrSingleton->GetValue()))
|
||||
sInfo.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
|
||||
auto ptrDependencies = ptrComponent->GetDirect("Dependencies");
|
||||
if (ptrDependencies)
|
||||
{
|
||||
auto ptrDependencyArray = ptrDependencies->GetArray();
|
||||
for (uint32_t uiDependencyIndex = 0; ptrDependencyArray && uiDependencyIndex < ptrDependencyArray->GetCount();
|
||||
uiDependencyIndex++)
|
||||
{
|
||||
auto ptrDependsOn = ptrDependencyArray->Get(uiDependencyIndex);
|
||||
if (ptrDependsOn)
|
||||
sInfo.seqDependencies.push_back(static_cast<sdv::u8string>(ptrDependsOn->GetValue()));
|
||||
}
|
||||
}
|
||||
|
||||
m_mapClassInfo[sInfo.ssClassName] = sInfo;
|
||||
}
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException&)
|
||||
{
|
||||
SDV_LOG_ERROR("Manifest interpretation error for: \"", m_pathModule.filename().generic_u8string(), "\"");
|
||||
return false;
|
||||
}
|
||||
|
||||
SDV_LOG_INFO("Successfully loaded SDV module: \"", m_pathModule.filename().generic_u8string(), "\"");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CModuleInst::Unload(bool bForce) noexcept
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
|
||||
|
||||
// Request the repository to unload any running objects
|
||||
try
|
||||
{
|
||||
GetRepository().DestroyModuleObjects(m_tModuleID);
|
||||
}
|
||||
catch (const sdv::XSysExcept&)
|
||||
{
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
}
|
||||
|
||||
// Check for a active objects first
|
||||
bool bHasActive = m_fnActiveObjects && m_fnActiveObjects();
|
||||
if (!bForce && bHasActive)
|
||||
{
|
||||
SDV_LOG_WARNING("Trying to unload module \"", m_pathModule.filename().generic_u8string(),
|
||||
"\" but there are still active objects. Unloading the module will be blocked.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear the members
|
||||
m_fnActiveObjects = nullptr;
|
||||
m_fnGetFactory = nullptr;
|
||||
m_pFactory = nullptr;
|
||||
m_mapClassInfo.clear();
|
||||
m_uiIfcVersion = 0u;
|
||||
|
||||
// Unload the module
|
||||
if (!bHasActive)
|
||||
{
|
||||
if (m_tModuleID && m_tModuleID != sdv::core::tCoreLibModule)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(reinterpret_cast<HMODULE>(m_tModuleID));
|
||||
#elif defined __unix__
|
||||
dlclose(reinterpret_cast<void*>(m_tModuleID));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
SDV_LOG_INFO("Successfully unloaded SDV module: \"", m_pathModule.filename().generic_u8string(), "\"");
|
||||
}
|
||||
} else
|
||||
{
|
||||
SDV_LOG_ERROR("Trying to force to unload module \"", m_pathModule.filename().generic_u8string(),
|
||||
"\" but there are still active objects. The module will not be unloaded! "
|
||||
"However, the management instance will be destroyed!");
|
||||
}
|
||||
|
||||
// Clear other members
|
||||
m_tModuleID = 0;
|
||||
m_pathModule.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CModuleInst::HasActiveObjects() const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
|
||||
|
||||
return m_fnActiveObjects && m_fnActiveObjects();
|
||||
}
|
||||
|
||||
#if defined _WIN32 && defined __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
170
sdv_services/core/module.h
Normal file
170
sdv_services/core/module.h
Normal file
@@ -0,0 +1,170 @@
|
||||
#ifndef MODULE_H
|
||||
#define MODULE_H
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <interfaces/core.h>
|
||||
#include <interfaces/module.h>
|
||||
|
||||
/**
|
||||
* @brief Module instance class
|
||||
*/
|
||||
class CModuleInst
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rpathModuleConfigPath Reference to the path of the module as it was specified in the configuration.
|
||||
* @param[in] rpathModule Reference to the absolute path of the module to load. If empty, the module is not instanceable, but
|
||||
* is used as a placeholder for rewriting the configuration.
|
||||
*/
|
||||
CModuleInst(const std::filesystem::path& rpathModuleConfigPath, const std::filesystem::path& rpathModule) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is not allowed.
|
||||
*/
|
||||
CModuleInst(const CModuleInst&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor is not allowed.
|
||||
*/
|
||||
CModuleInst(CModuleInst&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CModuleInst();
|
||||
|
||||
/**
|
||||
* @brief Assignment operator is not allowed.
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
CModuleInst& operator=(const CModuleInst&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move operator is not allowed.
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
CModuleInst& operator=(CModuleInst&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Return whether the module instance was loaded correctly.
|
||||
* @return Returns 'true' when the module was lloaded successfully. Otherwise returns 'false'.
|
||||
*/
|
||||
bool IsValid() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the default object name.
|
||||
* @param[in] ssClassName Name of the class
|
||||
* @return Returns the default object name or an empty string if the class doesn't exist.
|
||||
*/
|
||||
std::string GetDefaultObjectName(const std::string& ssClassName) const;
|
||||
|
||||
/**
|
||||
* @brief Is the object class marked as singleton (only one instance is allowed)?
|
||||
* @param[in] ssClassName Name of the class
|
||||
* @return Returns whether the object class is a singleton.
|
||||
*/
|
||||
bool IsSingleton(const std::string& ssClassName) const;
|
||||
|
||||
/**
|
||||
* @brief Gets the set of all available classes in the module
|
||||
* @returns Returns the vector of all available classes in the module
|
||||
*/
|
||||
std::vector<std::string> GetAvailableClasses() const;
|
||||
|
||||
/**
|
||||
* @brief Creates an object instance of a given SDV class via the loaded module
|
||||
* @param[in] ssClassName the class from which an object is to be created
|
||||
* @return Returns a pointer to the created object or nullptr in case of failure
|
||||
*/
|
||||
sdv::IInterfaceAccess* CreateObject(const std::string& ssClassName);
|
||||
|
||||
/**
|
||||
* @brief Destroys an object previously created via CreateObject
|
||||
* @param[in] object The object to be destroyed.
|
||||
* The object is only destroyed if it has been allocated via the loaded module and has not yet been destroyed.
|
||||
*/
|
||||
void DestroyObject(sdv::IInterfaceAccess* object);
|
||||
|
||||
/**
|
||||
* @brief Return the module config path (as it was specified in the configuration).
|
||||
* @return The config path of the module.
|
||||
*/
|
||||
std::filesystem::path GetModuleConfigPath() const;
|
||||
|
||||
/**
|
||||
* @brief Return the module path.
|
||||
* @return The path of the module.
|
||||
*/
|
||||
std::filesystem::path GetModulePath() const;
|
||||
|
||||
/**
|
||||
* @brief Return the module ID.
|
||||
* @return The path of the module.
|
||||
*/
|
||||
sdv::core::TModuleID GetModuleID() const;
|
||||
|
||||
/**
|
||||
* @brief Get the module information.
|
||||
* @return Returns the module information structure.
|
||||
*/
|
||||
sdv::core::SModuleInfo GetModuleInfo() const;
|
||||
|
||||
/**
|
||||
* @brief Return the class information of the supplied class name.
|
||||
* @param[in] rssClassName Reference to string containing the class name.
|
||||
* @return Returns the information structure of the requested class or an empty structure when the class could not be found.
|
||||
*/
|
||||
std::optional<sdv::SClassInfo> GetClassInfo(const std::string& rssClassName) const;
|
||||
|
||||
/**
|
||||
* @brief Load the module.
|
||||
* @param[in] rpathModule Reference to the path of the module to load.
|
||||
* @return Returns 'true' when successful; 'false' when not.
|
||||
*/
|
||||
bool Load(const std::filesystem::path& rpathModule) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Unload the module and clear the module instance class.
|
||||
* @attention If there are any active objects still running, this module is not unloaded. In cooperation with the bForce
|
||||
* flag this causes the module to stay in memory indefinitely.
|
||||
* @param[in] bForce When set, the module instance class is cleared whether or not the module is unloaded or not. Use with
|
||||
* extreme care (e.g. during shutdown).
|
||||
* @return Returns 'true' when successfully cleared the module instance class, 'false' when there are still active objects
|
||||
* blocking the unloading.
|
||||
*/
|
||||
bool Unload(bool bForce) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Return whether active objects created through this module are still running.
|
||||
* @return Returns 'true' when active objects are running; 'false' when not.
|
||||
*/
|
||||
bool HasActiveObjects() const;
|
||||
|
||||
private:
|
||||
/// HasActiveObjects exported function type definition.
|
||||
using TFNHasActiveObjects = bool();
|
||||
|
||||
/// GetModuleFactory exported function type definition.
|
||||
using TFNGetModuleFactory = sdv::IInterfaceAccess*(uint32_t);
|
||||
|
||||
/// GetManifest exported function type definition.
|
||||
using TFNGetManifest = const char*();
|
||||
|
||||
mutable std::recursive_mutex m_mtxModule; ///< Access regulation for the class members.
|
||||
std::filesystem::path m_pathModuleConfigPath; ///< Module path as it was specified in the configuration.
|
||||
std::filesystem::path m_pathModule; ///< Module path or filename.
|
||||
sdv::core::TModuleID m_tModuleID = 0; ///< Module handle.
|
||||
std::function<TFNHasActiveObjects> m_fnActiveObjects; ///< Module exported function for requesting active objects.
|
||||
std::function<TFNGetModuleFactory> m_fnGetFactory; ///< Module exported function for getting factory.
|
||||
std::function<TFNGetManifest> m_fnGetManifest; ///< Module exported function for getting manifest.
|
||||
sdv::IObjectFactory* m_pFactory = nullptr; ///< Factory interface of the module.
|
||||
std::map<std::string, sdv::SClassInfo> m_mapClassInfo; ///< Available classes
|
||||
uint32_t m_uiIfcVersion = 0; ///< Interface version.
|
||||
};
|
||||
|
||||
#endif // !defined MODULE_H
|
||||
411
sdv_services/core/module_control.cpp
Normal file
411
sdv_services/core/module_control.cpp
Normal file
@@ -0,0 +1,411 @@
|
||||
#include "module_control.h"
|
||||
#include "sdv_core.h"
|
||||
#include <algorithm>
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
#include "toml_parser/parser_toml.h"
|
||||
#include "toml_parser//parser_node_toml.h"
|
||||
|
||||
/// @cond DOXYGEN_IGNORE
|
||||
#ifdef _WIN32
|
||||
#include <Shlobj.h>
|
||||
#elif defined __unix__
|
||||
#include <ctime>
|
||||
#include <utime.h>
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
/// @endcond
|
||||
|
||||
CModuleControl::CModuleControl()
|
||||
{}
|
||||
|
||||
CModuleControl::~CModuleControl()
|
||||
{
|
||||
try
|
||||
{
|
||||
UnloadAll(std::vector<sdv::core::TModuleID>());
|
||||
}
|
||||
catch (const sdv::XSysExcept&)
|
||||
{
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool CModuleControl::AddModuleSearchDir(const sdv::u8string& ssDir)
|
||||
{
|
||||
// Add initial paths if not done so already
|
||||
AddCurrentPath();
|
||||
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
|
||||
std::filesystem::path pathDir(ssDir.c_str());
|
||||
|
||||
// Relative paths are always relative to the executable
|
||||
if (pathDir.is_relative())
|
||||
pathDir = GetExecDirectory() / ssDir.c_str();
|
||||
|
||||
// Remove any indirections
|
||||
pathDir = pathDir.lexically_normal();
|
||||
|
||||
// If the current path is not a directory, it cannot be added
|
||||
if (!std::filesystem::is_directory(pathDir)) return false;
|
||||
|
||||
// Check whether the path is already in the list
|
||||
if (std::find(m_lstSearchPaths.begin(), m_lstSearchPaths.end(), pathDir) != m_lstSearchPaths.end())
|
||||
return true; // This is not an error
|
||||
|
||||
// Add the path
|
||||
m_lstSearchPaths.push_back(pathDir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::u8string> CModuleControl::GetModuleSearchDirs() const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
sdv::sequence<sdv::u8string> seqSearchDirs;
|
||||
for (const std::filesystem::path& rpathSearchDir : m_lstSearchPaths)
|
||||
seqSearchDirs.push_back(rpathSearchDir.generic_u8string());
|
||||
return seqSearchDirs;
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::core::SModuleInfo> CModuleControl::GetModuleList() const
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
sdv::sequence<sdv::core::SModuleInfo> seqModuleInfos;
|
||||
|
||||
// Build the module information sequence
|
||||
for (const std::shared_ptr<CModuleInst>& ptrModule : m_lstModules)
|
||||
{
|
||||
if (!ptrModule) continue;
|
||||
seqModuleInfos.push_back(ptrModule->GetModuleInfo());
|
||||
}
|
||||
return seqModuleInfos;
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::SClassInfo> CModuleControl::GetClassList(/*in*/ sdv::core::TModuleID tModuleID) const
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
sdv::sequence<sdv::SClassInfo> seqClassInfos;
|
||||
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
|
||||
{
|
||||
return ptrModule && ptrModule->GetModuleID() == tModuleID;
|
||||
});
|
||||
if (itModule == m_lstModules.end()) return seqClassInfos; // Cannot find the module.
|
||||
|
||||
// Request the class infos from the module instance.
|
||||
std::vector<std::string> vecClasses = (*itModule)->GetAvailableClasses();
|
||||
for (const std::string& rssClass : vecClasses)
|
||||
{
|
||||
auto optClassInfo = (*itModule)->GetClassInfo(rssClass);
|
||||
if (!optClassInfo) continue;
|
||||
seqClassInfos.push_back(*optClassInfo);
|
||||
}
|
||||
return seqClassInfos;
|
||||
}
|
||||
|
||||
sdv::core::TModuleID CModuleControl::Load(const sdv::u8string& ssModulePath)
|
||||
{
|
||||
if(ssModulePath.empty())
|
||||
return 0;
|
||||
|
||||
// Add initial paths if not done so already
|
||||
AddCurrentPath();
|
||||
|
||||
// Core services bypass.
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
std::filesystem::path pathModule;
|
||||
if (ssModulePath == "core_services.sdv")
|
||||
pathModule = static_cast<std::string>(ssModulePath);
|
||||
else
|
||||
{
|
||||
if (GetAppControl().IsMainApplication() || GetAppControl().IsIsolatedApplication())
|
||||
{
|
||||
// Check the installation for the module.
|
||||
pathModule = GetAppConfig().FindInstalledModule(static_cast<std::string>(ssModulePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the search paths if the module path is relative
|
||||
std::list<std::filesystem::path> lstSearchPaths;
|
||||
std::filesystem::path pathSupplied(static_cast<std::string>(ssModulePath));
|
||||
if (pathSupplied.is_relative())
|
||||
lstSearchPaths = m_lstSearchPaths;
|
||||
|
||||
// Add an empty path to allow the OS to search when our own search paths could not find the module.
|
||||
lstSearchPaths.push_back(std::filesystem::path());
|
||||
|
||||
// Run through the search paths and try to find the module.
|
||||
for (const std::filesystem::path& rpathDirectory : lstSearchPaths)
|
||||
{
|
||||
// Compose the path
|
||||
std::filesystem::path pathModuleTemp;
|
||||
if (rpathDirectory.is_absolute())
|
||||
pathModuleTemp = (rpathDirectory / pathSupplied).lexically_normal();
|
||||
else
|
||||
{
|
||||
if (rpathDirectory.empty())
|
||||
pathModuleTemp = pathSupplied.lexically_normal();
|
||||
else
|
||||
pathModuleTemp = (GetExecDirectory() / rpathDirectory / pathSupplied).lexically_normal();
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(pathModuleTemp))
|
||||
{
|
||||
pathModule = pathModuleTemp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception& re)
|
||||
{
|
||||
SDV_LOG_ERROR("Supplied path to module load is not valid \"", ssModulePath, "\": ", re.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for an exisiting module. If existing, return the existing module.
|
||||
for (const std::shared_ptr<CModuleInst>& rptrModule : m_lstModules)
|
||||
{
|
||||
if (rptrModule && rptrModule->GetModulePath() == pathModule)
|
||||
return rptrModule->GetModuleID();
|
||||
}
|
||||
|
||||
// Create a new instance (even if the module could not be found).
|
||||
std::shared_ptr<CModuleInst> ptrModule = std::make_shared<CModuleInst>(static_cast<std::string>(ssModulePath), pathModule);
|
||||
m_lstModules.push_back(ptrModule);
|
||||
m_setConfigModules.insert(ptrModule->GetModuleID());
|
||||
if (!ptrModule->IsValid())
|
||||
{
|
||||
SDV_LOG_ERROR("The module was not found \"", ssModulePath, "\"");
|
||||
return 0; // Do not return the module ID of not loadable modules.
|
||||
}
|
||||
return ptrModule->GetModuleID();
|
||||
}
|
||||
|
||||
bool CModuleControl::Unload(sdv::core::TModuleID tModuleID)
|
||||
{
|
||||
// Cannot unload the core services
|
||||
if (tModuleID == sdv::core::tCoreLibModule)
|
||||
return true;
|
||||
|
||||
return ContextUnload(tModuleID, false);
|
||||
}
|
||||
|
||||
bool CModuleControl::HasActiveObjects(sdv::core::TModuleID tModuleID) const
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
|
||||
{
|
||||
return ptrModule && ptrModule->GetModuleID() == tModuleID;
|
||||
});
|
||||
if (itModule == m_lstModules.end()) return false; // Cannot find the module; no active objects known.
|
||||
|
||||
// Redirect the call to the module instance.
|
||||
return (*itModule)->HasActiveObjects();
|
||||
}
|
||||
|
||||
std::shared_ptr<CModuleInst> CModuleControl::FindModuleByClass(const std::string& rssClass)
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
for (const std::shared_ptr<CModuleInst>& ptrModule : m_lstModules)
|
||||
{
|
||||
if (!ptrModule) continue;
|
||||
std::vector<std::string> vecClasses = ptrModule->GetAvailableClasses();
|
||||
for (const std::string& rssClassLocal : vecClasses)
|
||||
{
|
||||
if (rssClassLocal == rssClass) return ptrModule;
|
||||
auto optClassInfo = ptrModule->GetClassInfo(rssClass);
|
||||
if (!optClassInfo) continue;
|
||||
if (std::find(optClassInfo->seqClassAliases.begin(), optClassInfo->seqClassAliases.end(), rssClass) !=
|
||||
optClassInfo->seqClassAliases.end())
|
||||
return ptrModule;
|
||||
}
|
||||
}
|
||||
|
||||
// For main and isolated applications, check whether the module is in one of the installation manifests.
|
||||
auto optManifest = GetAppConfig().FindInstalledComponent(rssClass);
|
||||
if (!optManifest) return nullptr;
|
||||
auto ssManifest = GetAppConfig().FindInstalledModuleManifest(optManifest->pathRelModule);
|
||||
if (ssManifest.empty()) return nullptr;
|
||||
lock.unlock();
|
||||
|
||||
// Load the module
|
||||
return GetModule(ContextLoad(optManifest->pathRelModule, ssManifest));
|
||||
}
|
||||
|
||||
std::shared_ptr<CModuleInst> CModuleControl::GetModule(sdv::core::TModuleID tModuleID) const
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
|
||||
{
|
||||
return ptrModule && ptrModule->GetModuleID() == tModuleID;
|
||||
});
|
||||
if (itModule == m_lstModules.end()) return nullptr; // Cannot find the module.
|
||||
return *itModule;
|
||||
}
|
||||
|
||||
void CModuleControl::UnloadAll(const std::vector<sdv::core::TModuleID>& rvecIgnoreModules)
|
||||
{
|
||||
// Force unloading all modules in reverse order of their loading
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
std::list<std::shared_ptr<CModuleInst>> lstCopy = m_lstModules;
|
||||
lock.unlock();
|
||||
while (lstCopy.size())
|
||||
{
|
||||
std::shared_ptr<CModuleInst> ptrModule = std::move(lstCopy.back());
|
||||
lstCopy.pop_back();
|
||||
if (!ptrModule) continue;
|
||||
|
||||
// On the ignore list?
|
||||
if (std::find(rvecIgnoreModules.begin(), rvecIgnoreModules.end(), ptrModule->GetModuleID()) != rvecIgnoreModules.end())
|
||||
continue;
|
||||
|
||||
// Unload... -> call the module control function to remove it from the list.
|
||||
ContextUnload(ptrModule->GetModuleID(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void CModuleControl::ResetConfigBaseline()
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
m_setConfigModules.clear();
|
||||
}
|
||||
|
||||
std::string CModuleControl::SaveConfig(const std::set<std::filesystem::path>& rsetIgnoreModule)
|
||||
{
|
||||
std::stringstream sstream;
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
|
||||
// Add all the loaded modules
|
||||
for (const std::shared_ptr<CModuleInst>& rptrModule : m_lstModules)
|
||||
{
|
||||
if (m_setConfigModules.find(rptrModule->GetModuleID()) != m_setConfigModules.end() &&
|
||||
rsetIgnoreModule.find(rptrModule->GetModuleConfigPath()) != rsetIgnoreModule.end())
|
||||
{
|
||||
sstream << std::endl;
|
||||
sstream << "[[Module]]" << std::endl;
|
||||
sstream << "Path = \"" << rptrModule->GetModuleConfigPath().generic_u8string() << "\"" << std::endl;
|
||||
}
|
||||
}
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
sdv::core::TModuleID CModuleControl::ContextLoad(const std::filesystem::path& rpathModule, const std::string& rssManifest)
|
||||
{
|
||||
if (GetAppControl().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
|
||||
[[maybe_unused]] bool bIsolationNeeded = false;
|
||||
try
|
||||
{
|
||||
size_t nIndex = 0;
|
||||
size_t nUnsupportedObjectCount = 0;
|
||||
CParserTOML parser(rssManifest);
|
||||
do
|
||||
{
|
||||
std::shared_ptr<CNode> ptrComponentType =
|
||||
parser.GetRoot().GetDirect(std::string("Component[") + std::to_string(nIndex++) + "].Type");
|
||||
if (!ptrComponentType) break;
|
||||
std::string ssType = static_cast<std::string>(ptrComponentType->GetValue());
|
||||
if (ssType == "Device") continue; // Okay
|
||||
if (ssType == "BasicService") continue; // Okay
|
||||
if (ssType == "Proxy") continue; // Okay
|
||||
if (ssType == "Stub") continue; // Okay
|
||||
if (ssType == "ComplexService" || ssType == "Application" || ssType == "Utility")
|
||||
{
|
||||
bIsolationNeeded = true;
|
||||
continue; // Okay
|
||||
}
|
||||
|
||||
// Not okay
|
||||
nUnsupportedObjectCount++;
|
||||
} while (true);
|
||||
if (nIndex == nUnsupportedObjectCount) return 0; // No object to load.
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException& /*rexcept*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: If the module to load needs isolation...
|
||||
// If this is a main application: isolate modules that should be isolated
|
||||
// If this is an isolated application: check for another loaded module that needs isolation already. If this do not allow
|
||||
// this module to load.
|
||||
|
||||
// Load the module
|
||||
return Load(rpathModule.generic_u8string());
|
||||
}
|
||||
|
||||
bool CModuleControl::ContextUnload(sdv::core::TModuleID tModuleID, bool bForce)
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
|
||||
{
|
||||
return ptrModule && ptrModule->GetModuleID() == tModuleID;
|
||||
});
|
||||
if (itModule == m_lstModules.end()) return true; // Cannot find the module to unload. This is not an error!
|
||||
m_setConfigModules.erase(tModuleID); // Remove from config if part of it.
|
||||
|
||||
// Check whether it is possible to unload.
|
||||
bool bSuccess = (*itModule)->Unload(bForce);
|
||||
if (bSuccess)
|
||||
m_lstModules.erase(itModule);
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
std::shared_ptr<CModuleInst> CModuleControl::FindModuleByPath(const std::filesystem::path& rpathModule) const
|
||||
{
|
||||
// Find the module instance
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
|
||||
{
|
||||
return ptrModule && ptrModule->GetModulePath() == rpathModule;
|
||||
});
|
||||
if (itModule == m_lstModules.end()) return nullptr; // Cannot find the module
|
||||
return *itModule;
|
||||
}
|
||||
|
||||
void CModuleControl::AddCurrentPath()
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
|
||||
|
||||
// Do this only once.
|
||||
if (!m_lstSearchPaths.empty()) return;
|
||||
|
||||
// Add the core directory
|
||||
std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal();
|
||||
m_lstSearchPaths.push_back(pathCoreDir);
|
||||
|
||||
// Add the exe dir
|
||||
std::filesystem::path pathExeDir = GetExecDirectory().lexically_normal();
|
||||
if (pathExeDir != pathCoreDir) m_lstSearchPaths.push_back(pathExeDir);
|
||||
}
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
CModuleControl& CModuleControlService::GetModuleControl()
|
||||
{
|
||||
return ::GetModuleControl();
|
||||
}
|
||||
|
||||
bool CModuleControlService::EnableModuleControlAccess()
|
||||
{
|
||||
return GetAppControl().IsStandaloneApplication() ||
|
||||
GetAppControl().IsEssentialApplication();
|
||||
}
|
||||
|
||||
#endif
|
||||
265
sdv_services/core/module_control.h
Normal file
265
sdv_services/core/module_control.h
Normal file
@@ -0,0 +1,265 @@
|
||||
/**
|
||||
* @file module_control.h
|
||||
* @author Sudipta Babu Durjoy FRD DISDS1 (mailto:sudipta.durjoy@zf.com) & Erik Verhoeven FRD DISDS1 (mailto:erik.verhoeven@zf.com)
|
||||
* @brief This file contains the implementation for loading VAPI/SDV modules and accessing the exported functions related to
|
||||
* VAPI/SDV modules on Windows and Posix
|
||||
* @version 2.0
|
||||
* @date 2024-04-03
|
||||
*
|
||||
* @copyright Copyright ZF Friedrichshafen AG (c) 2021-2025
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MODULE_CONTROL_H
|
||||
#define MODULE_CONTROL_H
|
||||
|
||||
#include <support/interface_ptr.h>
|
||||
#include <support/local_service_access.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include "toml_parser/parser_toml.h"
|
||||
#include "module.h"
|
||||
|
||||
/**
|
||||
* @brief Module control class
|
||||
* @remarks This class is also used to load the object from the core library without reloading the library itself again. It uses
|
||||
* the module file name "core_services.sdv" and the ID -1 to identify the library.
|
||||
*/
|
||||
class CModuleControl : public sdv::IInterfaceAccess, public sdv::core::IModuleControl, public sdv::core::IModuleControlConfig,
|
||||
public sdv::core::IModuleInfo
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CModuleControl();
|
||||
|
||||
/**
|
||||
* @brief No copy constructor
|
||||
* @param[in] rctrl Reference to the module control class.
|
||||
*/
|
||||
CModuleControl(const CModuleControl& rctrl) = delete;
|
||||
|
||||
/**
|
||||
* @brief No move constructor
|
||||
* @param[in] rctrl Reference to the module control class.
|
||||
*/
|
||||
CModuleControl(CModuleControl&& rctrl) = delete;
|
||||
|
||||
/**
|
||||
* @brief Destructor unloads all modules
|
||||
*/
|
||||
virtual ~CModuleControl();
|
||||
|
||||
// Interface map
|
||||
// NOTE: Only the sdv::core::IModuleControlConfig interface should be accessible from here, since the module control is
|
||||
// accessible through sdv::GetCore() function, which should not make all other interfaces accessible (the system needs to start
|
||||
// and access should happen over sdv::core::GetObject()).
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IModuleControlConfig)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief No assignment operator
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
CModuleControl& operator=(const CModuleControl&) = delete;
|
||||
|
||||
/**
|
||||
* @brief No move operator
|
||||
* @return Returns a reference to this class.
|
||||
*/
|
||||
CModuleControl& operator=(CModuleControl&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Sets a search path to a folder where a module can be found. Overload of
|
||||
* sdv::core::IModuleControlConfig::AddModuleSearchDir.
|
||||
* @param[in] ssDir Relative or absolute path to an existing folder.
|
||||
* To load a module out of the folder only the filename is required.
|
||||
* @return returns true if folder exists, otherwise false
|
||||
*/
|
||||
virtual bool AddModuleSearchDir(const sdv::u8string& ssDir) override;
|
||||
|
||||
/**
|
||||
* @brief Get a sequence containing the current module search directories.
|
||||
* @return The sequence with the module search directories.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::u8string> GetModuleSearchDirs() const override;
|
||||
|
||||
/**
|
||||
* @brief Get a list of loaded modules. Overload of sdv::core::IModuleInfo::GetModuleList.
|
||||
* @return The module list.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::core::SModuleInfo> GetModuleList() const override;
|
||||
|
||||
/**
|
||||
* @brief Get a list of classes exposed by the provided module.
|
||||
* @param[in] tModuleID The module ID to request the list of classes for.
|
||||
* @return Sequence with the class information structures.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::SClassInfo> GetClassList(/*in*/ sdv::core::TModuleID tModuleID) const override;
|
||||
|
||||
/**
|
||||
* @brief Load the module. Overload of sdv::core::IModuleControl::Load.
|
||||
* @param[in] ssModulePath File name of the module to load.
|
||||
* @return Returns the ID of the module or 0 if the module could not be loaded
|
||||
*/
|
||||
virtual sdv::core::TModuleID Load(const sdv::u8string& ssModulePath) override;
|
||||
|
||||
/**
|
||||
* @brief Unload the module. Overload of sdv::core::IModuleControl::Unload.
|
||||
* @param[in] tModuleID Id representing a module, which has previous been loaded by Load
|
||||
* @return Returns true if module is unloaded successfully otherwise false.
|
||||
*/
|
||||
virtual bool Unload(sdv::core::TModuleID tModuleID) override;
|
||||
|
||||
/**
|
||||
* @brief Checking for active objects. Overload of sdv::core::IModuleControl::HasActiveObjects.
|
||||
* @param[in] tModuleID Id representing a module, which has previous been loaded by Load
|
||||
* @return Returns true if there is an active object otherwise false.
|
||||
*/
|
||||
virtual bool HasActiveObjects(sdv::core::TModuleID tModuleID) const override;
|
||||
|
||||
/**
|
||||
* @brief Find a module containing a class with the specified name.
|
||||
* @details Find the first module containing a class with the specified name. For main and isolated applications, if the
|
||||
* module wasn't loaded, the installation manifests are checked and if a module with the class has been found, the module will
|
||||
* be loaded. The order of checking the installation manifest is core-manifest, manifest in executable directory and manifest
|
||||
* in supplied installation directory.
|
||||
* @remarks Modules of system objects specified in the user installation are not returned.
|
||||
* @param[in] rssClass Reference to the class that should be searched for. The class is compared to the class name and the
|
||||
* default name in the manifest.
|
||||
* @return Returns a smart pointer to the module instance or NULL when the module hasn't been found.
|
||||
*/
|
||||
std::shared_ptr<CModuleInst> FindModuleByClass(const std::string& rssClass);
|
||||
|
||||
/**
|
||||
* @brief Get a class instance for the module.
|
||||
* @param[in] tModuleID Id representing a module, which has previous been loaded by Load
|
||||
* @return Returns a smart pointer to the module instance or NULL when the module hasn't been found.
|
||||
*/
|
||||
std::shared_ptr<CModuleInst> GetModule(sdv::core::TModuleID tModuleID) const;
|
||||
|
||||
/**
|
||||
* @brief Unload all modules (with force-flag).
|
||||
* @param[in] rvecIgnoreModules Reference to a vector containing all the modules to not unload.
|
||||
*/
|
||||
void UnloadAll(const std::vector<sdv::core::TModuleID>& rvecIgnoreModules);
|
||||
|
||||
/**
|
||||
* @brief Reset the current config baseline.
|
||||
*/
|
||||
void ResetConfigBaseline();
|
||||
|
||||
/**
|
||||
* @brief Save the configuration of all modules.
|
||||
* @param[in] rsetIgnoreModule Set of modules not needing to add.
|
||||
* @return The string containing all the modules.
|
||||
*/
|
||||
std::string SaveConfig(const std::set<std::filesystem::path>& rsetIgnoreModule);
|
||||
|
||||
/**
|
||||
* @brief Load the module with the supplied module manifest. Only for main and isolated applications.
|
||||
* @remarks When running the main application all complex services, applications and utilities run in their isolated
|
||||
* environment. If multiple components are implemented in the module, of which one is suppose to run isolated, all of them run
|
||||
* isolated. Isolated applications cannot load other modules if one was loaded already. Maintenance applications cannot load
|
||||
* modules. All other applications load modules in their own process. Exception to the rul are proxy and stub components.
|
||||
* System objects cannot be loaded this way at all.
|
||||
* @param[in] rpathModule Absolute file path of the module to load.
|
||||
* @param[in] rssManifest The module manifest deciding on where the module should run.
|
||||
* @return Returns the ID of the module or 0 if the module could not be loaded
|
||||
*/
|
||||
sdv::core::TModuleID ContextLoad(const std::filesystem::path& rpathModule, const std::string& rssManifest);
|
||||
|
||||
/**
|
||||
* @brief Unload the module.
|
||||
* @param[in] tModuleID Id representing a module, which has previous been loaded by ContextLoad.
|
||||
* @param[in] bForce When set, the module instance class is cleared whether or not the module is unloaded or not. Use with
|
||||
* extreme care (e.g. during shutdown).
|
||||
* @return Returns true if module is unloaded successfully otherwise false.
|
||||
*/
|
||||
bool ContextUnload(sdv::core::TModuleID tModuleID, bool bForce);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Find a module by its path.
|
||||
* @param[in] rpathModule File path of the module to load. Relative paths are relative to the installation directory for main
|
||||
* and isolated applications and relative to the exe for all other applications.
|
||||
* @return Returns a smart pointer to the module instance or NULL when the module hasn't been found (wasn't loaded).
|
||||
*/
|
||||
std::shared_ptr<CModuleInst> FindModuleByPath(const std::filesystem::path& rpathModule) const;
|
||||
|
||||
/**
|
||||
* @brief HasActiveObjects exported function type definition.
|
||||
*/
|
||||
using TFNHasActiveObjects = bool();
|
||||
|
||||
/**
|
||||
* @brief GetModuleFactory exported function type definition.
|
||||
*/
|
||||
using TFNGetModuleFactory = sdv::IInterfaceAccess*(uint32_t);
|
||||
|
||||
/**
|
||||
* @brief Add core_services and executable folders to the search paths if not done so already.
|
||||
*/
|
||||
void AddCurrentPath();
|
||||
|
||||
using TConfigSet = std::set<sdv::core::TModuleID>;
|
||||
mutable std::recursive_mutex m_mtxModules; ///< Control of the loaded library access
|
||||
std::list<std::shared_ptr<CModuleInst>> m_lstModules; ///< List of modules. The order of loading is preserved.
|
||||
std::list<std::filesystem::path> m_lstSearchPaths; ///< List of search directories.
|
||||
TConfigSet m_setConfigModules; ///< Set with the modules for storing in the configuration.
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
/**
|
||||
* @brief Module control service
|
||||
*/
|
||||
class CModuleControlService : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
CModuleControlService() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(GetModuleControl())
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IModuleInfo, GetModuleControl())
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableModuleControlAccess(), 1)
|
||||
SDV_INTERFACE_SECTION(1)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IModuleControl, GetModuleControl())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("ModuleControlService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Get access to the module control
|
||||
* @return Returns the one global instance of the module control.
|
||||
*/
|
||||
static CModuleControl& GetModuleControl();
|
||||
|
||||
/**
|
||||
* @brief When set, the module control will be enabled.
|
||||
* @return Returns whether access to the module control is granted.
|
||||
*/
|
||||
static bool EnableModuleControlAccess();
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CModuleControlService)
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#endif // !define MODULE_CONTROL_H
|
||||
43
sdv_services/core/object_lifetime_control.cpp
Normal file
43
sdv_services/core/object_lifetime_control.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "object_lifetime_control.h"
|
||||
#include "sdv_core.h"
|
||||
|
||||
CObjectLifetimeControl::CObjectLifetimeControl(sdv::IInterfaceAccess* pObject, IObjectDestroyHandler& rHandler,
|
||||
bool bAutoDestroy /*= true*/) : m_rHandler(rHandler), m_ptrObject(pObject), m_bAutoDestroy(bAutoDestroy)
|
||||
{
|
||||
m_pObjectDestroy = m_ptrObject.GetInterface<sdv::IObjectDestroy>();
|
||||
m_pObjectLifetime = m_ptrObject.GetInterface<sdv::IObjectLifetime>();
|
||||
}
|
||||
|
||||
void CObjectLifetimeControl::DestroyObject()
|
||||
{
|
||||
if (m_pObjectDestroy)
|
||||
m_pObjectDestroy->DestroyObject();
|
||||
else if (m_pObjectLifetime)
|
||||
m_pObjectLifetime->Decrement();
|
||||
|
||||
m_rHandler.OnDestroyObject(m_ptrObject);
|
||||
|
||||
if (m_bAutoDestroy) delete this;
|
||||
}
|
||||
|
||||
void CObjectLifetimeControl::Increment()
|
||||
{
|
||||
if (m_pObjectLifetime) m_pObjectLifetime->Increment();
|
||||
}
|
||||
|
||||
bool CObjectLifetimeControl::Decrement()
|
||||
{
|
||||
bool bRet = false;
|
||||
if (m_pObjectLifetime) bRet = m_pObjectLifetime->Decrement();
|
||||
if (bRet)
|
||||
{
|
||||
m_rHandler.OnDestroyObject(m_ptrObject);
|
||||
if (m_bAutoDestroy) delete this;
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
|
||||
uint32_t CObjectLifetimeControl::GetCount() const
|
||||
{
|
||||
return m_pObjectLifetime ? m_pObjectLifetime->GetCount() : 0u;
|
||||
}
|
||||
87
sdv_services/core/object_lifetime_control.h
Normal file
87
sdv_services/core/object_lifetime_control.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#ifndef OBJECT_LIFETIME_CONTROL_H
|
||||
#define OBJECT_LIFETIME_CONTROL_H
|
||||
|
||||
#include <support/interface_ptr.h>
|
||||
|
||||
/**
|
||||
* @brief Object destroy handler - callback interface
|
||||
*/
|
||||
interface IObjectDestroyHandler
|
||||
{
|
||||
/**
|
||||
* @brief Inform about an object destruction. This function might delete the calling class, so no other function must be
|
||||
* called after the call to this function.
|
||||
* @param[in] pObject Pointer to the object being destroyed.
|
||||
*/
|
||||
virtual void OnDestroyObject(sdv::IInterfaceAccess* pObject) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wrapper class around the object to allow objects to be destroyed through sdv::IObjectDestroy.
|
||||
*/
|
||||
class CObjectLifetimeControl : public sdv::IInterfaceAccess, public sdv::IObjectDestroy, public sdv::IObjectLifetime
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor assigning the object instance.
|
||||
* @param[in] pObject Interface pointer to the object instance. Must not be nullptr.
|
||||
* @param[in] rHandler Reference to the handler being informed about the object destruction.
|
||||
* @param[in] bAutoDestroy When set, the wrapper class will be destroyed automatically when the object is destroyed.
|
||||
*/
|
||||
CObjectLifetimeControl(sdv::IInterfaceAccess* pObject, IObjectDestroyHandler& rHandler, bool bAutoDestroy = true);
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor
|
||||
*/
|
||||
virtual ~CObjectLifetimeControl() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(!m_pObjectLifetime, 1)
|
||||
SDV_INTERFACE_SECTION(1)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(m_pObjectLifetime, 2)
|
||||
SDV_INTERFACE_SECTION(2)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectLifetime)
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
SDV_INTERFACE_DENY_ENTRY(sdv::IObjectControl) // Do not allow users to directly access the object control.
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_ptrObject)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
|
||||
* @attention After a call of this function, all exposed interfaces render invalid and should not be used any more.
|
||||
*/
|
||||
virtual void DestroyObject() override;
|
||||
|
||||
/**
|
||||
* @brief Increment the lifetime. Needs to be balanced by a call to Decrement. Overload of sdv::IObjectLifetime::Increment.
|
||||
*/
|
||||
virtual void Increment() override;
|
||||
|
||||
/**
|
||||
* @brief Decrement the lifetime. If the lifetime reaches zero, the object will be destroyed (through the exposed
|
||||
* IObjectDestroy interface). Overload of sdv::IObjectLifetime::Decrement.
|
||||
* @return Returns 'true' if the object was destroyed, false if not.
|
||||
*/
|
||||
virtual bool Decrement() override;
|
||||
|
||||
/**
|
||||
* @brief Get the current lifetime count. Overload of sdv::IObjectLifetime::GetCount.
|
||||
* @remarks The GetCount function returns a momentary value, which can be changed at any moment.
|
||||
* @return Returns the current counter value.
|
||||
*/
|
||||
virtual uint32_t GetCount() const override;
|
||||
|
||||
private:
|
||||
IObjectDestroyHandler& m_rHandler; ///< Reference to the handler class being informed about the
|
||||
///< destruction.
|
||||
sdv::IObjectDestroy* m_pObjectDestroy = nullptr; ///< IObjectDestroy interface is exposed by object
|
||||
sdv::IObjectLifetime* m_pObjectLifetime = nullptr; ///< IObjectLifetime interface is exposed by object
|
||||
sdv::TInterfaceAccessPtr m_ptrObject; ///< Interface pointer to the object.
|
||||
bool m_bAutoDestroy = false; ///< When set, the lifetime wrapper object is destroyed after an
|
||||
///< object destroy call.
|
||||
};
|
||||
|
||||
#endif // !defined OBJECT_LIFETIME_CONTROL_H
|
||||
1229
sdv_services/core/repository.cpp
Normal file
1229
sdv_services/core/repository.cpp
Normal file
File diff suppressed because it is too large
Load Diff
397
sdv_services/core/repository.h
Normal file
397
sdv_services/core/repository.h
Normal file
@@ -0,0 +1,397 @@
|
||||
|
||||
#ifndef VAPI_REPOSITORY_H
|
||||
#define VAPI_REPOSITORY_H
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <shared_mutex>
|
||||
#include <interfaces/repository.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <condition_variable>
|
||||
#include "module.h"
|
||||
#include "object_lifetime_control.h"
|
||||
#include "iso_monitor.h"
|
||||
|
||||
/**
|
||||
* @brief repository service providing functionality to load modules, create objects and access exiting objects
|
||||
*/
|
||||
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:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CRepository() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IObjectAccess)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IRepositoryMarshallCreate)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IRepositoryUtilityCreate)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Switch all components in config mode.
|
||||
*/
|
||||
void SetConfigMode();
|
||||
|
||||
/**
|
||||
* @brief Switch all components in running mode.
|
||||
*/
|
||||
void SetRunningMode();
|
||||
|
||||
/**
|
||||
* @brief Get the object instance previously created through the repository service. Overload of IObjectAccess::GetObject.
|
||||
* @param[in] ssObjectName The name of the requested object.
|
||||
* @return Returns the IInterfaceAccess interface of the object instance if found and nullptr otherwise.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* GetObject(/*in*/ const sdv::u8string& ssObjectName) override;
|
||||
|
||||
/**
|
||||
* @brief Get the object instance previously created through the repository service.
|
||||
* @attention Utilities cannot be returned by this function.
|
||||
* @param[in] tObjectID The ID of the requested object.
|
||||
* @return Returns the IInterfaceAccess interface of the object instance if found and nullptr otherwise.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* GetObjectByID(/*in*/ sdv::core::TObjectID tObjectID) override;
|
||||
|
||||
/**
|
||||
* @brief Creates an utility object from a previously loaded module. Overload of IRepositoryUtilityCreate::CreateUtility.
|
||||
* @attention Utilities are standalone objects. Use IObjectDestroy to destroy the utility.
|
||||
* @param[in] ssClassName The name of the object class to be created.
|
||||
* @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl.
|
||||
* @return Returns the IInterfaceAccess interface of newly created utility.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* CreateUtility(/*in*/ const sdv::u8string& ssClassName,
|
||||
/*in*/ const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Create a proxy object for the interface with the supplied ID. If successful, this object is initialized, but
|
||||
* not linked to any other object within the system. Overload of sdv::core::IRepositoryMarshallCreate::CreateProxyObject.
|
||||
* @details Create a proxy object with the name "proxy_<ifc id>". "ifc_id" is the decimal value of an interface ID.
|
||||
* @param[in] id The interface ID to create the object for.
|
||||
* @return Returns the interface to the proxy object. Destruction of the object can be achieved through IObjectDestroy.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* CreateProxyObject(/*in*/ sdv::interface_id id) override;
|
||||
|
||||
/**
|
||||
* @brief Create a stub object for the interface with the supplied ID. If successful, this object is initialized, but
|
||||
* not linked to any other object within the system. Overload of sdv::core::IRepositoryMarshallCreate::CreateStubObject.
|
||||
* @details Create a stub object with the name "stub_<ifc id>". "ifc_id" is the decimal value of an interface ID.
|
||||
* @param[in] id The interface ID to create the object for.
|
||||
* @return Returns the interface to the stub object. Destruction of the object can be achieved through IObjectDestroy.
|
||||
*/
|
||||
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.
|
||||
* @details For standalone and essential applications, this function allows the creation of system, device and service
|
||||
* objects if the module was loaded previously. For the main and isolated application, this function allows the
|
||||
* creation of complex services only and only those that are in the installation. For the isolated application only one
|
||||
* complex service can be created. External apps, utilities, and proxy and stub objects cannot be created at all using
|
||||
* this function.
|
||||
* Objects that the to be create object is depending on will be created as well. For the main application this is
|
||||
* limited to complex services. Isolated applications cannot load other services; this is taken over by the main
|
||||
* application.
|
||||
* @param[in] ssClassName The name of the object class to be created. For the main application, the class string could
|
||||
* be empty for the main application if the object was defined in the installation.
|
||||
* @param[in] ssObjectName Name of the object, required to be unique. For standalone and essential applications, the
|
||||
* name string can be empty, in which case the object might either provide a name proposal or the name is the same as
|
||||
* 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.
|
||||
* @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.
|
||||
*/
|
||||
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.
|
||||
* @param[in] tModuleID Module ID that contains the object class to create the object with.
|
||||
* @param[in] ssClassName The name of the object class to be created.
|
||||
* @param[in] ssObjectName Optional name of the object - required to be unique. If not supplied, the object might
|
||||
* either provide a name proposal or the name is the same as 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.
|
||||
* @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.
|
||||
*/
|
||||
virtual sdv::core::TObjectID CreateObjectFromModule(/*in*/ sdv::core::TModuleID tModuleID,
|
||||
/*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
|
||||
* 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.
|
||||
* @return Returns whether the object destruction was successful.
|
||||
*/
|
||||
virtual bool DestroyObject(/*in*/ const sdv::u8string& ssObjectName) override;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @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
|
||||
* be empty for the main application if the object was defined in the installation.
|
||||
* @param[in] ssObjectName Name of the object, required to be unique. For standalone and essential applications, the
|
||||
* name string can be empty, in which case the object might either provide a name proposal or the name is the same as
|
||||
* 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.
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @brief Destroy a previously created object with the supplied name. Internal function not accessible through the interface.
|
||||
* @details For standalone and essential applications previously created system, device and service objects can be
|
||||
* 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.
|
||||
* @return Returns whether the object destruction was successful.
|
||||
*/
|
||||
bool DestroyObject2(/*in*/ const sdv::u8string& ssObjectName);
|
||||
|
||||
/**
|
||||
* @brief Register as foreign object and make it public to the system with the given name. Overload of
|
||||
* sdv::core::IRegisterForeignObject::RegisterObject.
|
||||
* @param[in] pObjectIfc Interface of the object to be registered.
|
||||
* @param[in] ssObjectName The name under which the object - required to be unique.
|
||||
* @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.
|
||||
*/
|
||||
virtual sdv::core::TObjectID RegisterObject(/*in*/ sdv::IInterfaceAccess* pObjectIfc,
|
||||
/*in*/ const sdv::u8string& ssObjectName) override;
|
||||
|
||||
/**
|
||||
* @brief Register the core repository.
|
||||
* @param[in] pCoreRepository Pointer to the proxy interface of the core repository.
|
||||
*/
|
||||
virtual void LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository) override;
|
||||
|
||||
/**
|
||||
* @brief Unlink a previously linked core repository.
|
||||
*/
|
||||
virtual void UnlinkCoreRepository() override;
|
||||
|
||||
/**
|
||||
* @brief Find the class information of an object with the supplied name. Overload of
|
||||
* sdv::core::IRepositoryControl::IRepositoryInfo::FindClass.
|
||||
* @param[in] ssClassName Object class name.
|
||||
* @return The object class information.
|
||||
*/
|
||||
virtual sdv::SClassInfo FindClass(/*in*/ const sdv::u8string& ssClassName) const override;
|
||||
|
||||
/**
|
||||
* @brief Get a list of all the instantiated objects. Overload of sdv::core::IRepositoryControl::IRepositoryInfo::GetObjectList.
|
||||
* @return Sequence containing the object information structures.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::core::SObjectInfo> GetObjectList() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the object info for the requested object. Overload of sdv::core::IRepositoryInfo::GetObjectInfo.
|
||||
* @param[in] tObjectID The object ID to return the object information for.
|
||||
* @return The object information structure if the object is available or an empty structure if not.
|
||||
*/
|
||||
virtual sdv::core::SObjectInfo GetObjectInfo(/*in*/ sdv::core::TObjectID tObjectID) const override;
|
||||
|
||||
/**
|
||||
* @brief Find an object with the supplied name. Only object instances that are in the service list can be found with this
|
||||
* function (devices, basic and complex services, and system objects). Overload of sdv::core::IRepositoryInfo::FindObject.
|
||||
* @param[in] ssObjectName Object name to search for.
|
||||
* @return The object information structure if the object is available or an empty structure if not.
|
||||
*/
|
||||
virtual sdv::core::SObjectInfo FindObject(/*in*/ const sdv::u8string& ssObjectName) const override;
|
||||
|
||||
/**
|
||||
* @brief Remove an object instance from the local object map. Overload of IObjectDestroyHandler::OnDestroyObject.
|
||||
* @param[in] pObject Interface pointer to the object instance.
|
||||
*/
|
||||
virtual void OnDestroyObject(sdv::IInterfaceAccess* pObject) override;
|
||||
|
||||
/**
|
||||
* @brief Destroy all objects from the service map created for a specific module.
|
||||
* @param[in] tModuleID Module ID that contains the object class to destroy.
|
||||
*/
|
||||
void DestroyModuleObjects(sdv::core::TModuleID tModuleID);
|
||||
|
||||
/**
|
||||
* @brief Destroy all objects from the service map.
|
||||
* @remarks In Emergency, the objects should be removed without unloading to prevent any more calls to take place. The reason
|
||||
* is, that the system could do an emergency shutdown and part of the system might have been cleaned up already and part not.
|
||||
* @param[in] rvecIgnoreObjects Reference to the vector of objects to not destroy.
|
||||
* @param[in] bForce Force destruction (remove without calling any more function).
|
||||
*/
|
||||
void DestroyAllObjects(const std::vector<std::string>&rvecIgnoreObjects, bool bForce = false);
|
||||
|
||||
/**
|
||||
* @brief Reset the current config baseline.
|
||||
*/
|
||||
void ResetConfigBaseline();
|
||||
|
||||
/**
|
||||
* @brief Save the configuration of all components.
|
||||
* @return The string containing all the components.
|
||||
*/
|
||||
std::string SaveConfig();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Create an isolated object (object running in a separate process). Only allowed to be called by the main application.
|
||||
* @param[in] rsClassInfo Reference to the class information structure of the object.
|
||||
* @param[in] rssObjectName Reference to the name of the object - required to be unique.
|
||||
* @param[in] rssObjectConfig Reference to an optional configuration handed over to the object upon creation via IObjectControl.
|
||||
* @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 CreateIsolatedObject(const sdv::SClassInfo& rsClassInfo, const sdv::u8string& rssObjectName,
|
||||
const sdv::u8string& rssObjectConfig);
|
||||
|
||||
/**
|
||||
* @brief Creates an object from the supplied module instance.
|
||||
* @param[in] rptrModule Reference to the module that contains the object class to create the object with.
|
||||
* @param[in] rsClassInfo Reference to the class information structure of the object.
|
||||
* @param[in] rssObjectName Reference to the name of the object - required to be unique.
|
||||
* @param[in] rssObjectConfig Reference to an optional configuration handed over to the object upon creation via IObjectControl.
|
||||
* @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 InternalCreateObject(const std::shared_ptr<CModuleInst>& rptrModule, const sdv::SClassInfo& rsClassInfo,
|
||||
const sdv::u8string& rssObjectName, const sdv::u8string& rssObjectConfig);
|
||||
|
||||
/**
|
||||
* @brief Create a new unique object ID.
|
||||
* @return The created object ID.
|
||||
*/
|
||||
static sdv::core::TObjectID CreateObjectID();
|
||||
|
||||
/**
|
||||
* @brief Get a list of depending object instances of a specific class.
|
||||
* @param[in] rssClass Reference to the class name.
|
||||
* @return Returns a list of object instances.
|
||||
*/
|
||||
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>>;
|
||||
using TServiceMap = std::map<std::string, TOrderedObjectList::iterator>;
|
||||
using TIsolationMap = std::map<std::string, std::shared_ptr<SObjectEntry>>;
|
||||
using TLocalObjectMap = std::map<sdv::IInterfaceAccess*, std::shared_ptr<SObjectEntry>>;
|
||||
using TConfigSet = std::set<sdv::core::TObjectID>;
|
||||
mutable std::shared_mutex m_mtxObjects; ///< Protects against concurrent access.
|
||||
TOrderedObjectList m_lstOrderedServiceObjects; ///< List of service object IDs in order of creation.
|
||||
TServiceMap m_mapServiceObjects; ///< Map of service objects indexed by object name an pointing
|
||||
///< to the entry in the list.
|
||||
TLocalObjectMap m_mapLocalObjects; ///< Map with local objects indexed by object pointer.
|
||||
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).
|
||||
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.
|
||||
};
|
||||
|
||||
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
|
||||
|
||||
/**
|
||||
* @brief Repository service
|
||||
*/
|
||||
class CRepositoryService : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
CRepositoryService() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(GetRepository())
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IRepositoryInfo, GetRepository())
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableRepositoryObjectControl(), 1)
|
||||
SDV_INTERFACE_SECTION(1)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IRepositoryControl, GetRepository())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableRepositoryRegisterForeignApp(), 2)
|
||||
SDV_INTERFACE_SECTION(2)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IRegisterForeignObject, GetRepository())
|
||||
SDV_INTERFACE_DEFAULT_SECTION()
|
||||
SDV_INTERFACE_SET_SECTION_CONDITION(EnableRepositoryLink(), 3)
|
||||
SDV_INTERFACE_SECTION(3)
|
||||
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::ILinkCoreRepository, GetRepository())
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("RepositoryService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Get access to the repository.
|
||||
* @return Returns a reference to the one repository of this module.
|
||||
*/
|
||||
static CRepository& GetRepository();
|
||||
|
||||
/**
|
||||
* @brief When set, the repository object control access will be enabled.
|
||||
* @return Returns whether object control interface access is granted.
|
||||
*/
|
||||
static bool EnableRepositoryObjectControl();
|
||||
|
||||
/**
|
||||
* @brief When set, the foreign app registration into the repository will be enabled.
|
||||
* @return Returns whether foreign app rgeistration interface access is granted.
|
||||
*/
|
||||
static bool EnableRepositoryRegisterForeignApp();
|
||||
|
||||
/**
|
||||
* @brief When set, the core repository link access will be enabled.
|
||||
* @return Returns whether core repository link interface access is granted.
|
||||
*/
|
||||
static bool EnableRepositoryLink();
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CRepositoryService)
|
||||
#endif
|
||||
|
||||
#endif // !define VAPI_REPOSITORY_H
|
||||
140
sdv_services/core/sdv_core.cpp
Normal file
140
sdv_services/core/sdv_core.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "sdv_core.h"
|
||||
#include "toml_parser_util.h" // Insert to create the factory of this utility
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
extern sdv::IInterfaceAccess* SDVCore()
|
||||
{
|
||||
static CSDVCore& rcore = CSDVCore::GetInstance();
|
||||
return &rcore;
|
||||
}
|
||||
|
||||
CSDVCore::CSDVCore()
|
||||
{}
|
||||
|
||||
CSDVCore::~CSDVCore()
|
||||
{}
|
||||
|
||||
CSDVCore& CSDVCore::GetInstance()
|
||||
{
|
||||
static CSDVCore core;
|
||||
return core;
|
||||
}
|
||||
|
||||
CAppControl& CSDVCore::GetAppControl()
|
||||
{
|
||||
return m_appctrl;
|
||||
}
|
||||
|
||||
CModuleControl& CSDVCore::GetModuleControl()
|
||||
{
|
||||
return m_modulectrl;
|
||||
}
|
||||
|
||||
CMemoryManager& CSDVCore::GetMemoryManager()
|
||||
{
|
||||
return m_memmgr;
|
||||
}
|
||||
|
||||
CRepository& CSDVCore::GetRepository()
|
||||
{
|
||||
return m_repository;
|
||||
}
|
||||
|
||||
CLoggerControl& CSDVCore::GetLoggerControl()
|
||||
{
|
||||
return m_loggerctrl;
|
||||
}
|
||||
|
||||
CLogger& CSDVCore::GetDefaultLogger()
|
||||
{
|
||||
return m_defaultlogger;
|
||||
}
|
||||
|
||||
CAppConfig& CSDVCore::GetAppConfig()
|
||||
{
|
||||
return m_appconfig;
|
||||
}
|
||||
|
||||
CAppControl& GetAppControl()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetAppControl();
|
||||
}
|
||||
|
||||
CModuleControl& GetModuleControl()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetModuleControl();
|
||||
}
|
||||
|
||||
CMemoryManager& GetMemoryManager()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetMemoryManager();
|
||||
}
|
||||
|
||||
CRepository& GetRepository()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetRepository();
|
||||
}
|
||||
|
||||
CLoggerControl& GetLoggerControl()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetLoggerControl();
|
||||
}
|
||||
|
||||
CLogger& GetDefaultLogger()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetDefaultLogger();
|
||||
}
|
||||
|
||||
CAppConfig& GetAppConfig()
|
||||
{
|
||||
return CSDVCore::GetInstance().GetAppConfig();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
150
sdv_services/core/sdv_core.h
Normal file
150
sdv_services/core/sdv_core.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#ifndef CORE_H
|
||||
#define CORE_H
|
||||
|
||||
#include <support/component_impl.h>
|
||||
#include "app_control.h"
|
||||
#include "module_control.h"
|
||||
#include "memory.h"
|
||||
#include "repository.h"
|
||||
#include "logger_control.h"
|
||||
#include "logger.h"
|
||||
#include "app_config.h"
|
||||
|
||||
/**
|
||||
* @brief SDV core instance class containing containing the instances for the core services.
|
||||
*/
|
||||
class CSDVCore : public sdv::IInterfaceAccess
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CSDVCore();
|
||||
|
||||
/**
|
||||
* \brief Destructor
|
||||
*/
|
||||
~CSDVCore();
|
||||
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_appctrl)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_modulectrl)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_memmgr)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_repository)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_loggerctrl)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief The one and only instance.
|
||||
* @return Reference to this class.
|
||||
*/
|
||||
static CSDVCore& GetInstance();
|
||||
|
||||
/**
|
||||
* @brief Return the application control.
|
||||
* @return Reference to the application control.
|
||||
*/
|
||||
CAppControl& GetAppControl();
|
||||
|
||||
/**
|
||||
* @brief Return the module control.
|
||||
* @return Reference to the module control.
|
||||
*/
|
||||
CModuleControl& GetModuleControl();
|
||||
|
||||
/**
|
||||
* @brief Return the memory manager.
|
||||
* @return Reference to the memory manager.
|
||||
*/
|
||||
CMemoryManager& GetMemoryManager();
|
||||
|
||||
/**
|
||||
* @brief Return the repository.
|
||||
* @return Reference to the repository.
|
||||
*/
|
||||
CRepository& GetRepository();
|
||||
|
||||
/**
|
||||
* @brief Return the logger control.
|
||||
* @return Reference to the logger control.
|
||||
*/
|
||||
CLoggerControl& GetLoggerControl();
|
||||
|
||||
/**
|
||||
* @brief Return the default logger.
|
||||
* @return Reference to the default logger.
|
||||
*/
|
||||
CLogger& GetDefaultLogger();
|
||||
|
||||
/**
|
||||
* @brief Return the application config class.
|
||||
* @return Reference to the application config class.
|
||||
*/
|
||||
CAppConfig& GetAppConfig();
|
||||
|
||||
private:
|
||||
CMemoryManager m_memmgr; ///< Memory manager - note: needs to be first in the list of members!
|
||||
CAppControl m_appctrl; ///< Application control
|
||||
CRepository m_repository; ///< Repository - note: repository should be present before module control!
|
||||
CModuleControl m_modulectrl; ///< Module control
|
||||
CLogger m_defaultlogger; ///< Default logger - note: the logger must be present before the logger control!
|
||||
CLoggerControl m_loggerctrl; ///< Logger control
|
||||
CAppConfig m_appconfig; ///< Application configuration class
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Exported function for core access.
|
||||
* @return Pointer to the interface of the core library.
|
||||
*/
|
||||
extern "C" SDV_SYMBOL_PUBLIC sdv::IInterfaceAccess* SDVCore();
|
||||
|
||||
/**
|
||||
* @brief Return the application control.
|
||||
* @return Reference to the application control.
|
||||
*/
|
||||
CAppControl& GetAppControl();
|
||||
|
||||
/**
|
||||
* @brief Return the module control.
|
||||
* @return Reference to the module control.
|
||||
*/
|
||||
CModuleControl& GetModuleControl();
|
||||
|
||||
/**
|
||||
* @brief Return the memory manager.
|
||||
* @return Reference to the memory manager.
|
||||
*/
|
||||
CMemoryManager& GetMemoryManager();
|
||||
|
||||
/**
|
||||
* @brief Return the repository.
|
||||
* @return Reference to the repository.
|
||||
*/
|
||||
CRepository& GetRepository();
|
||||
|
||||
/**
|
||||
* @brief Return the logger control.
|
||||
* @return Reference to the logger control.
|
||||
*/
|
||||
CLoggerControl& GetLoggerControl();
|
||||
|
||||
/**
|
||||
* @brief Return the default logger.
|
||||
* @attention Use the logger control to access the logger.
|
||||
* @return Reference to the default logger.
|
||||
*/
|
||||
CLogger& GetDefaultLogger();
|
||||
|
||||
/**
|
||||
* @brief Return the application config class.
|
||||
* @return Reference to the application config class.
|
||||
*/
|
||||
CAppConfig& GetAppConfig();
|
||||
|
||||
/**
|
||||
* @brief Get the location of the core_services.sdv.
|
||||
* @return Path to the directory containing the loaded core directory.
|
||||
*/
|
||||
std::filesystem::path GetCoreDirectory();
|
||||
|
||||
#endif // !defined CORE_H
|
||||
323
sdv_services/core/toml_parser/character_reader_utf_8.cpp
Normal file
323
sdv_services/core/toml_parser/character_reader_utf_8.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
#include "character_reader_utf_8.h"
|
||||
#include "exception.h"
|
||||
|
||||
CCharacterReaderUTF8::CCharacterReaderUTF8() : m_nDataLength{0}, m_nCursor{0}
|
||||
{}
|
||||
|
||||
CCharacterReaderUTF8::CCharacterReaderUTF8(const std::string& rssString) :
|
||||
m_ssString{rssString}, m_nDataLength{rssString.size()}, m_nCursor{0}
|
||||
{
|
||||
CheckForInvalidUTF8Bytes();
|
||||
CheckForInvalidUTF8Sequences();
|
||||
}
|
||||
|
||||
void CCharacterReaderUTF8::Feed(const std::string& rssString)
|
||||
{
|
||||
m_ssString = rssString;
|
||||
m_nDataLength = rssString.size();
|
||||
m_nCursor = 0;
|
||||
|
||||
CheckForInvalidUTF8Bytes();
|
||||
CheckForInvalidUTF8Sequences();
|
||||
}
|
||||
|
||||
void CCharacterReaderUTF8::Reset()
|
||||
{
|
||||
m_ssString.clear();
|
||||
m_nDataLength = 0;
|
||||
m_nCursor = 0;
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::Peek()
|
||||
{
|
||||
return GetNextCharacter();
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::Peek(std::size_t n)
|
||||
{
|
||||
if (n < 1)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
size_t offset{0};
|
||||
for (std::size_t i = 0; i < n - 1; ++i)
|
||||
{
|
||||
offset += GetLengthOfNextCharacter(offset);
|
||||
if (m_nDataLength <= m_nCursor + offset)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return GetNextCharacter(offset);
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::PeekUntil(const std::vector<std::string>& lstCollection)
|
||||
{
|
||||
size_t offset{0};
|
||||
bool found{false};
|
||||
std::string accumulation;
|
||||
while (!found && m_nDataLength > m_nCursor + offset)
|
||||
{
|
||||
std::string character = GetNextCharacter(offset);
|
||||
offset += GetLengthOfNextCharacter(offset);
|
||||
for (const auto& delimiter : lstCollection)
|
||||
{
|
||||
if (delimiter == character)
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
accumulation += character;
|
||||
}
|
||||
}
|
||||
return accumulation;
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::Consume()
|
||||
{
|
||||
if (IsEOF())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
std::string character{GetNextCharacter()};
|
||||
m_nCursor += GetLengthOfNextCharacter();
|
||||
return character;
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::Consume(std::size_t n)
|
||||
{
|
||||
if (n < 1)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
size_t offset{0};
|
||||
for (uint32_t i = 0; i < n - 1; ++i)
|
||||
{
|
||||
offset += GetLengthOfNextCharacter(offset);
|
||||
if (m_nDataLength < m_nCursor + offset)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
std::string character{GetNextCharacter(offset)};
|
||||
m_nCursor += offset + GetLengthOfNextCharacter(offset);
|
||||
return character;
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::ConsumeUntil(const std::vector<std::string>& lstCollection)
|
||||
{
|
||||
std::size_t offset{0};
|
||||
bool found{false};
|
||||
std::string accumulation;
|
||||
while (!found && m_nDataLength > m_nCursor + offset)
|
||||
{
|
||||
std::string character = GetNextCharacter(offset);
|
||||
offset += GetLengthOfNextCharacter(offset);
|
||||
for (const auto& delimiter : lstCollection)
|
||||
{
|
||||
if (delimiter == character)
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
accumulation += character;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset -= character.size();
|
||||
}
|
||||
}
|
||||
m_nCursor += offset;
|
||||
return accumulation;
|
||||
}
|
||||
|
||||
bool CCharacterReaderUTF8::IsEOF() const
|
||||
{
|
||||
return m_nDataLength < m_nCursor + 1;
|
||||
}
|
||||
|
||||
void CCharacterReaderUTF8::CheckForInvalidUTF8Bytes() const
|
||||
{
|
||||
const unsigned char invalidByteC0{0xC0};
|
||||
const unsigned char invalidByteC1{0xC1};
|
||||
const unsigned char lowerBoundInvalidRegion{0xF5};
|
||||
for (std::size_t i = 0; i < m_ssString.size(); ++i)
|
||||
{
|
||||
unsigned char uc = m_ssString[i];
|
||||
if (uc == invalidByteC0 || uc == invalidByteC1 || uc >= lowerBoundInvalidRegion)
|
||||
{
|
||||
std::stringstream message;
|
||||
message << "Invalid byte " << std::hex << uc << std::dec << " at position " << i << "\n";
|
||||
throw XTOMLParseException(message.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CCharacterReaderUTF8::CheckForInvalidUTF8Sequences() const
|
||||
{
|
||||
enum class EState
|
||||
{
|
||||
state_neutral,
|
||||
state_two_byte,
|
||||
state_three_byte,
|
||||
state_four_byte,
|
||||
state_error
|
||||
};
|
||||
EState eCurrentState{EState::state_neutral};
|
||||
uint8_t uiIndex{0};
|
||||
auto fnCheckByteInNeutralState = [&uiIndex, &eCurrentState](unsigned char uc)
|
||||
{
|
||||
if ((uc & m_uiOneByteCheckMask) == m_OneByteCheckValue)
|
||||
{
|
||||
eCurrentState = EState::state_neutral;
|
||||
uiIndex = 0;
|
||||
}
|
||||
else if ((uc & m_uiFourByteCheckMask) == m_uiFourByteCheckValue)
|
||||
{
|
||||
eCurrentState = EState::state_four_byte;
|
||||
uiIndex = 1;
|
||||
}
|
||||
else if ((uc & m_uiThreeByteCheckMask) == m_uiThreeByteCheckValue)
|
||||
{
|
||||
eCurrentState = EState::state_three_byte;
|
||||
uiIndex = 1;
|
||||
}
|
||||
else if ((uc & m_uiTwoByteCheckMask) == m_uiTwoByteCheckValue)
|
||||
{
|
||||
eCurrentState = EState::state_two_byte;
|
||||
uiIndex = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
eCurrentState = EState::state_error;
|
||||
}
|
||||
};
|
||||
auto fnCheckByteInTwoByteState = [&uiIndex, &eCurrentState](unsigned char uc)
|
||||
{
|
||||
if ((uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
|
||||
{
|
||||
uiIndex = 0;
|
||||
eCurrentState = EState::state_neutral;
|
||||
}
|
||||
else
|
||||
{
|
||||
eCurrentState = EState::state_error;
|
||||
}
|
||||
};
|
||||
auto fnCheckByteInThreeByteState = [&uiIndex, &eCurrentState](unsigned char uc)
|
||||
{
|
||||
if (uiIndex == 1 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
|
||||
{
|
||||
uiIndex = 2;
|
||||
}
|
||||
else if (uiIndex == 2 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
|
||||
{
|
||||
uiIndex = 0;
|
||||
eCurrentState = EState::state_neutral;
|
||||
}
|
||||
else
|
||||
{
|
||||
eCurrentState = EState::state_error;
|
||||
}
|
||||
};
|
||||
auto fnCheckByteInFourByteState = [&uiIndex, &eCurrentState](unsigned char uc)
|
||||
{
|
||||
if (uiIndex <= 2 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
|
||||
{
|
||||
++uiIndex;
|
||||
}
|
||||
else if (uiIndex == 3 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
|
||||
{
|
||||
uiIndex = 0;
|
||||
eCurrentState = EState::state_neutral;
|
||||
}
|
||||
else
|
||||
{
|
||||
eCurrentState = EState::state_error;
|
||||
}
|
||||
};
|
||||
for (std::size_t i = 0; i < m_ssString.size(); ++i)
|
||||
{
|
||||
uint8_t uiCurrentByte = m_ssString[i];
|
||||
switch (eCurrentState)
|
||||
{
|
||||
case EState::state_neutral:
|
||||
fnCheckByteInNeutralState(uiCurrentByte);
|
||||
break;
|
||||
case EState::state_two_byte:
|
||||
fnCheckByteInTwoByteState(uiCurrentByte);
|
||||
break;
|
||||
case EState::state_three_byte:
|
||||
fnCheckByteInThreeByteState(uiCurrentByte);
|
||||
break;
|
||||
case EState::state_four_byte:
|
||||
fnCheckByteInFourByteState(uiCurrentByte);
|
||||
break;
|
||||
default:
|
||||
std::stringstream sstreamMessage;
|
||||
sstreamMessage << "Invalid character with byte " << std::hex << m_ssString[i - 1] << std::dec << "("
|
||||
<< static_cast<int32_t>(m_ssString[i - 1]) << ") at index " << i - 1 << "\n";
|
||||
throw XTOMLParseException(sstreamMessage.str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (eCurrentState != EState::state_neutral)
|
||||
{
|
||||
std::stringstream sstreamMessage;
|
||||
sstreamMessage << "Unfinished character at the end of file\n";
|
||||
throw XTOMLParseException(sstreamMessage.str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::GetNextCharacter()
|
||||
{
|
||||
if (IsEOF())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return {m_ssString, m_nCursor, GetLengthOfNextCharacter()};
|
||||
}
|
||||
|
||||
std::string CCharacterReaderUTF8::GetNextCharacter(std::size_t offset)
|
||||
{
|
||||
return {m_ssString, m_nCursor + offset, GetLengthOfNextCharacter(offset)};
|
||||
}
|
||||
|
||||
size_t CCharacterReaderUTF8::GetLengthOfNextCharacter() const
|
||||
{
|
||||
return GetLengthOfNextCharacter(0);
|
||||
}
|
||||
|
||||
size_t CCharacterReaderUTF8::GetLengthOfNextCharacter(std::size_t offset) const
|
||||
{
|
||||
uint8_t ui = m_ssString[m_nCursor + offset];
|
||||
int32_t ret;
|
||||
if ((ui & m_uiOneByteCheckMask) == m_OneByteCheckValue)
|
||||
{
|
||||
ret = 1;
|
||||
}
|
||||
else if ((ui & m_uiFourByteCheckMask) == m_uiFourByteCheckValue)
|
||||
{
|
||||
ret = 4;
|
||||
}
|
||||
else if ((ui & m_uiThreeByteCheckMask) == m_uiThreeByteCheckValue)
|
||||
{
|
||||
ret = 3;
|
||||
}
|
||||
else if ((ui & m_uiTwoByteCheckMask) == m_uiTwoByteCheckValue)
|
||||
{
|
||||
ret = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream sstreamMessage;
|
||||
sstreamMessage << "Invalid character sequence with byte " << std::hex << ui << std::dec
|
||||
<< " as start byte\n";
|
||||
throw XTOMLParseException(sstreamMessage.str());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
127
sdv_services/core/toml_parser/character_reader_utf_8.h
Normal file
127
sdv_services/core/toml_parser/character_reader_utf_8.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#ifndef CHARACTER_READER_UTF_8_H
|
||||
#define CHARACTER_READER_UTF_8_H
|
||||
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief Reads a given input string, interprets bytes as UTF-8 and returns UTF-8 characters or strings in order on
|
||||
* demand
|
||||
*/
|
||||
class CCharacterReaderUTF8
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Standard constructor for empty character reader
|
||||
*/
|
||||
CCharacterReaderUTF8();
|
||||
|
||||
/**
|
||||
* @brief Constructs a character reader from a given string
|
||||
* @param[in] rssString UTF-8 input string.
|
||||
* @throw InvalidCharacterException Throws an InvalidCharacterException if the input contains invalid UTF-8 characters
|
||||
* @throw InvalidByteException Throws an InvalidByteException if the input contains for UTF-8 invalid bytes
|
||||
*/
|
||||
CCharacterReaderUTF8(const std::string& rssString);
|
||||
|
||||
/**
|
||||
* @brief Feed the character reader from the given string.
|
||||
* @param[in] rssString UTF-8 input string.
|
||||
*/
|
||||
void Feed(const std::string& rssString);
|
||||
|
||||
/**
|
||||
* @brief Reset the character reader content.
|
||||
*/
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* @brief Gets the next UTF-8 character without advancing the cursor
|
||||
* @return Returns the next UTF-8 character after the current cursor or an empty string if the cursor is at the
|
||||
* end
|
||||
*/
|
||||
std::string Peek();
|
||||
|
||||
/**
|
||||
* @brief Gets the next n-th UTF-8 character without advancing the cursor
|
||||
* @param[in] n Step size
|
||||
* @return Returns the n-th UTF-8 character after the current cursor or an empty string if n<1 or it would read
|
||||
* after the last character
|
||||
*/
|
||||
std::string Peek(std::size_t n);
|
||||
|
||||
/**
|
||||
* @brief Gets all upcoming UTF-8 characters until a terminating character without advancing the cursor
|
||||
* @param[in] lstCollection A collection of terminating characters
|
||||
* @return Returns a string of UTF-8 characters until (excluding) the first occurrence of one of the given
|
||||
* characters
|
||||
*/
|
||||
std::string PeekUntil(const std::vector<std::string>& lstCollection);
|
||||
|
||||
/**
|
||||
* @brief Gets the next UTF-8 character and advancing the cursor by one
|
||||
* @return Returns the next UTF-8 character after the current cursor or an empty string if the cursor is at the
|
||||
* end
|
||||
*/
|
||||
std::string Consume();
|
||||
|
||||
/**
|
||||
* @brief Gets the next n-th UTF-8 character and advancing the cursor by n
|
||||
* @param[in] n Step size
|
||||
* @return Returns the n-th UTF-8 character after the current cursor or an empty string if n<1 or it would read
|
||||
* after the last character
|
||||
*/
|
||||
std::string Consume(std::size_t n);
|
||||
|
||||
/**
|
||||
* @brief Gets all upcoming UTF-8 characters until a terminating character and advancing the cursor by the number
|
||||
* of characters in the returned string
|
||||
* @param[in] lstCollection A collection of terminating characters
|
||||
* @return Returns a string of UTF-8 characters until excludingg) the first occurrence of one of the given
|
||||
* characters
|
||||
*/
|
||||
std::string ConsumeUntil(const std::vector<std::string>& lstCollection);
|
||||
|
||||
/**
|
||||
* @brief Checks if the cursor is at the end of the data to read
|
||||
* @return Returns true if the cursor is at the end of the readable data, false otherwise
|
||||
*/
|
||||
bool IsEOF() const;
|
||||
|
||||
private:
|
||||
void CheckForInvalidUTF8Bytes() const;
|
||||
|
||||
void CheckForInvalidUTF8Sequences() const;
|
||||
|
||||
std::string GetNextCharacter();
|
||||
|
||||
std::string GetNextCharacter(std::size_t offset);
|
||||
|
||||
size_t GetLengthOfNextCharacter() const;
|
||||
|
||||
size_t GetLengthOfNextCharacter(std::size_t offset) const;
|
||||
|
||||
static const uint8_t m_uiOneByteCheckMask{0b10000000}; //!< Checkmask for 1-Byte UTF-8 characters
|
||||
static const uint8_t m_OneByteCheckValue{0b00000000}; //!< Value of a 1-Byte UTF-8 character
|
||||
//!< after being or-ed with the checkmask
|
||||
static const uint8_t m_uiFollowByteCheckMask{0b11000000}; //!< Checkmask for followbyte of a multi-Byte UTF-8 character
|
||||
static const uint8_t m_uiFollowByteValue{0b10000000}; //!< Value of a followbyte of a multi-Byte UTF-8 character
|
||||
//!< after being or-ed with the checkmask
|
||||
static const uint8_t m_uiTwoByteCheckMask{0b11100000}; //!< Checkmask for startbyte of 2-Byte UTF-8 characters
|
||||
static const uint8_t m_uiTwoByteCheckValue{0b11000000}; //!< Value of a startbyte of a 2-Byte UTF-8 character
|
||||
//!< after being or-ed with the checkmask
|
||||
static const uint8_t m_uiThreeByteCheckMask{0b11110000}; //!< Checkmask for startbyte of 3-Byte UTF-8 characters
|
||||
static const uint8_t m_uiThreeByteCheckValue{0b11100000}; //!< Value of a startbyte of a 3-Byte UTF-8 character
|
||||
//!< after being or-ed with the checkmask
|
||||
static const uint8_t m_uiFourByteCheckMask{0b11111000}; //!< Checkmask for startbyte of 4-Byte UTF-8 characters
|
||||
static const uint8_t m_uiFourByteCheckValue{0b11110000}; //!< Value of a startbyte of a 4-Byte UTF-8 character
|
||||
//!< after being or-ed with the checkmask
|
||||
|
||||
std::string m_ssString;
|
||||
std::size_t m_nDataLength;
|
||||
std::size_t m_nCursor;
|
||||
};
|
||||
|
||||
#endif // CHARACTER_READER_UTF_8_H
|
||||
20
sdv_services/core/toml_parser/exception.h
Normal file
20
sdv_services/core/toml_parser/exception.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef CONFIG_EXCEPTION_H
|
||||
#define CONFIG_EXCEPTION_H
|
||||
|
||||
#include <interfaces/toml.h>
|
||||
|
||||
except XTOMLParseException : public sdv::toml::XTOMLParseException
|
||||
{
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
XTOMLParseException(const std::string& rss) { ssMessage = rss; };
|
||||
|
||||
/**
|
||||
* @brief Return the explanatory string.
|
||||
* @return The descriptive string.
|
||||
*/
|
||||
virtual const char* what() const noexcept override { return ssMessage.c_str(); }
|
||||
};
|
||||
|
||||
#endif // !defined CONFIG_EXCEPTION_H
|
||||
1022
sdv_services/core/toml_parser/lexer_toml.cpp
Normal file
1022
sdv_services/core/toml_parser/lexer_toml.cpp
Normal file
File diff suppressed because it is too large
Load Diff
226
sdv_services/core/toml_parser/lexer_toml.h
Normal file
226
sdv_services/core/toml_parser/lexer_toml.h
Normal file
@@ -0,0 +1,226 @@
|
||||
#ifndef LEXER_TOML_H
|
||||
#define LEXER_TOML_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <stack>
|
||||
#include "character_reader_utf_8.h"
|
||||
|
||||
/**
|
||||
* @brief Tokenizes the output of a character reader in regard of the TOML format and returns tokens in order on demand
|
||||
*/
|
||||
class CLexerTOML
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Enum for all possible token categories
|
||||
*/
|
||||
enum class ETokenCategory : uint8_t
|
||||
{
|
||||
token_none, ///< Default
|
||||
token_syntax_assignment, ///< '='
|
||||
token_syntax_array_open, ///< '[' after '='
|
||||
token_syntax_array_close, ///< ']' after an array open
|
||||
token_syntax_table_open, ///< '['
|
||||
token_syntax_table_close, ///< ']'
|
||||
token_syntax_table_array_open, ///< '[['
|
||||
token_syntax_table_array_close, ///< ']]'
|
||||
token_syntax_inline_table_open, ///< '{'
|
||||
token_syntax_inline_table_close, ///< '}'
|
||||
token_syntax_comma, ///< ','
|
||||
token_syntax_dot, ///< '.'
|
||||
token_syntax_new_line, ///< Line break
|
||||
token_key, ///< Key of a Key-Value-Pair
|
||||
token_string, ///< A string for a Value of a Key-Value-Pair or Array
|
||||
token_integer, ///< An integer for a Value of a Key-Value-Pair or Array
|
||||
token_float, ///< A floating point number for a Value of a Key-Value-Pair or Array
|
||||
token_boolean, ///< A bool for a Value of a Key-Value-Pair or Array
|
||||
token_time_local, ///< Unused for now
|
||||
token_date_time_offset, ///< Unused for now
|
||||
token_date_time_local, ///< Unused for now
|
||||
token_date_local, ///< Unused for now
|
||||
token_eof, ///< End of File Token; may only be at the end of the token array
|
||||
token_error, ///< Error token containing an error message; further lexing is not affected
|
||||
token_empty, ///< Empty token for trying to read out of bounds
|
||||
token_terminated, ///< Terminated token containing an error message; further lexing is terminated
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Contains lexed information for the parser
|
||||
*/
|
||||
struct SToken
|
||||
{
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
SToken() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a new Token object with a given category
|
||||
* @param[in] category The initial token category value for the token to be constructed
|
||||
*/
|
||||
explicit SToken(ETokenCategory category) : eCategory(category)
|
||||
{}
|
||||
|
||||
std::string ssContentString; ///< Token string content
|
||||
int64_t iContentInteger{}; ///< Token integer content
|
||||
double dContentFloatingpoint{}; ///< Token floatingpoint content
|
||||
bool bContentBoolean{}; ///< Token boolean content
|
||||
ETokenCategory eCategory{ETokenCategory::token_none}; ///< Token category
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CLexerTOML() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructs a new LexerTOML object with given input data that will be lexed
|
||||
* @param[in] rssString The UTF-8 encoded content of a TOML source
|
||||
*/
|
||||
CLexerTOML(const std::string& rssString);
|
||||
|
||||
/**
|
||||
* @brief Feed the lexer with the given string.
|
||||
* @param[in] rssString UTF-8 input string.
|
||||
*/
|
||||
void Feed(const std::string& rssString);
|
||||
|
||||
/**
|
||||
* @brief Reset the lexer content.
|
||||
*/
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* @brief Gets the next token after the current cursor position without advancing the cursor
|
||||
* @return Returns the next token after the current cursor position or a End-of-File-Token if there are no
|
||||
* tokens
|
||||
*/
|
||||
SToken Peek() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the next token after the current cursor position and advancing the cursor by one
|
||||
* @return Returns the next token after the current cursor position or a End-of-File-Token if there are no
|
||||
* tokens
|
||||
*/
|
||||
SToken Consume();
|
||||
|
||||
/**
|
||||
* @brief Gets the n-th token after the current cursor without advancing the cursor
|
||||
* @param[in] n Step size
|
||||
* @return Returns the n-th token after the current cursor position or and empty token if n<1 or the end-token
|
||||
* if a step of n would read a position after the end-token
|
||||
*/
|
||||
SToken Peek(int32_t n);
|
||||
|
||||
/**
|
||||
* @brief Gets the n-th token after the current cursor and advancing the cursor by n
|
||||
* @param[in] n Step size
|
||||
* @return Returns the n-th token after the current cursor position or and empty token if n<1 or the end-token
|
||||
* if a step of n would read a position after the end-token
|
||||
*/
|
||||
SToken Consume(int32_t n);
|
||||
|
||||
/**
|
||||
* @brief Checks if the end-token was consumed
|
||||
* @return Returns true if the end-token was consumed by Consume() or Consume(n) or if there are no tokens;
|
||||
* false otherwise
|
||||
*/
|
||||
bool IsEnd() const;
|
||||
|
||||
private:
|
||||
void GenerateTokens();
|
||||
|
||||
bool IsBasicQuotedKey();
|
||||
|
||||
void ReadBasicQuotedKey();
|
||||
|
||||
bool IsLiteralQuotedKey();
|
||||
|
||||
void ReadLiteralQuotedKey();
|
||||
|
||||
bool IsBareKey();
|
||||
|
||||
void ReadBareKey();
|
||||
|
||||
bool IsBasicString();
|
||||
|
||||
void ReadBasicString();
|
||||
|
||||
bool IsBasicMultilineString();
|
||||
|
||||
void ReadBasicMultilineString();
|
||||
|
||||
bool IsLiteralString();
|
||||
|
||||
void ReadLiteralString();
|
||||
|
||||
bool IsLiteralMultilineString();
|
||||
|
||||
void ReadLiteralMultilineString();
|
||||
|
||||
bool IsInteger();
|
||||
|
||||
void ReadInteger();
|
||||
|
||||
bool IsFloat();
|
||||
|
||||
void ReadFloat();
|
||||
|
||||
bool IsBool();
|
||||
|
||||
void ReadBool();
|
||||
|
||||
bool IsWhitespace();
|
||||
|
||||
void ReadWhitespace();
|
||||
|
||||
bool IsSyntaxElement();
|
||||
|
||||
void ReadSyntaxElement();
|
||||
|
||||
bool IsComment();
|
||||
|
||||
void ReadComment();
|
||||
|
||||
void ReadUnknownSequence();
|
||||
|
||||
std::string Unescape();
|
||||
|
||||
std::string Unicode4DigitToUTF8();
|
||||
|
||||
std::string Unicode8DigitToUTF8();
|
||||
|
||||
std::string UnicodeToUTF8(uint8_t numCharacters);
|
||||
|
||||
static uint32_t HexToDecimal(const char character);
|
||||
|
||||
static uint32_t DecimalToDecimal(const char character);
|
||||
|
||||
static uint32_t OctalToDecimal(const char character);
|
||||
|
||||
static uint32_t BinaryToDecimal(const char character);
|
||||
|
||||
CCharacterReaderUTF8 m_reader;
|
||||
std::vector<SToken> m_vecTokens;
|
||||
std::size_t m_nCursor{0};
|
||||
|
||||
/**
|
||||
* @brief Enum for differentiating between keys and values that are potentially indifferent like '"value" =
|
||||
* "value"'
|
||||
*/
|
||||
enum class EExpectation
|
||||
{
|
||||
expect_key, ///< A key is expected over a value
|
||||
expect_value, ///< A value is expected over a key
|
||||
expect_value_once, ///< A value is expected over a key once
|
||||
};
|
||||
std::stack<EExpectation> m_stackExpectations; ///< Tracking of key or value expectations in nested structures
|
||||
// int32_t m_LineCount{0};
|
||||
|
||||
const std::vector<std::string> m_vecKeyDelimiters{
|
||||
"\n", "\t", "\r", " ", "", ".", "=", "]"}; ///< Characters that delimit a key
|
||||
const std::vector<std::string> m_vecValueDelimiters{
|
||||
"\n", "\t", "\r", " ", ",", "", "]", "}"}; ///< Characters that delimit a value
|
||||
};
|
||||
|
||||
#endif // LEXER_TOML_H
|
||||
760
sdv_services/core/toml_parser/parser_node_toml.cpp
Normal file
760
sdv_services/core/toml_parser/parser_node_toml.cpp
Normal file
@@ -0,0 +1,760 @@
|
||||
#include <algorithm>
|
||||
#include "parser_node_toml.h"
|
||||
#include "exception.h"
|
||||
#include <sstream>
|
||||
|
||||
size_t FindFirst(const std::string& rss, const std::string& rssSeparator /*= "."*/)
|
||||
{
|
||||
enum class EType {normal, single_quoted_string, double_quoted_string} eType = EType::normal;
|
||||
size_t nPos = 0;
|
||||
while (nPos < rss.size())
|
||||
{
|
||||
switch (rss[nPos])
|
||||
{
|
||||
case '\'':
|
||||
if (eType == EType::normal)
|
||||
eType = EType::single_quoted_string;
|
||||
else if (eType == EType::single_quoted_string)
|
||||
eType = EType::normal;
|
||||
break;
|
||||
case '\"':
|
||||
if (eType == EType::normal)
|
||||
eType = EType::double_quoted_string;
|
||||
else if (eType == EType::double_quoted_string)
|
||||
eType = EType::normal;
|
||||
break;
|
||||
case '\\':
|
||||
nPos++;
|
||||
break;
|
||||
default:
|
||||
if (eType == EType::normal && rssSeparator.find(rss[nPos]) != std::string::npos)
|
||||
return nPos;
|
||||
break;
|
||||
}
|
||||
nPos++;
|
||||
}
|
||||
return nPos >= rss.size() ? std::string::npos : nPos;
|
||||
}
|
||||
|
||||
size_t FindLast(const std::string& rss, const std::string& rssSeparator /*= "."*/)
|
||||
{
|
||||
enum class EType {normal, single_quoted_string, double_quoted_string} eType = EType::normal;
|
||||
size_t nPos = rss.size();
|
||||
while (nPos)
|
||||
{
|
||||
nPos--;
|
||||
bool bEscaped = nPos && rss[nPos - 1] == '\\';
|
||||
switch (rss[nPos])
|
||||
{
|
||||
case '\'':
|
||||
if (bEscaped)
|
||||
nPos--;
|
||||
else if (eType == EType::normal)
|
||||
eType = EType::single_quoted_string;
|
||||
else if (eType == EType::single_quoted_string)
|
||||
eType = EType::normal;
|
||||
break;
|
||||
case '\"':
|
||||
if (bEscaped)
|
||||
nPos--;
|
||||
else if (eType == EType::normal)
|
||||
eType = EType::double_quoted_string;
|
||||
else if (eType == EType::double_quoted_string)
|
||||
eType = EType::normal;
|
||||
break;
|
||||
default:
|
||||
if (eType == EType::normal && rssSeparator.find(rss[nPos]) != std::string::npos)
|
||||
return nPos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
bool CompareEqual(const std::string& rss1, const std::string& rss2)
|
||||
{
|
||||
size_t nStart1 = 0, nStop1 = rss1.size();
|
||||
if (rss1.size() && rss1.find_first_of("\"\'") == 0 && rss1.find_last_of("\"\'") == (rss1.size() - 1))
|
||||
{
|
||||
nStart1++;
|
||||
nStop1--;
|
||||
}
|
||||
size_t nStart2 = 0, nStop2 = rss2.size();
|
||||
if (rss2.size() && rss2.find_first_of("\"\'") == 0 && rss2.find_last_of("\"\'") == (rss2.size() - 1))
|
||||
{
|
||||
nStart2++;
|
||||
nStop2--;
|
||||
}
|
||||
|
||||
if (nStop1 - nStart1 != nStop2 - nStart2) return false;
|
||||
for (size_t n = 0; n < (nStop1 - nStart1); n++)
|
||||
{
|
||||
if (rss1[nStart1 + n] != rss2[nStart2 + n])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string EscapeString(const std::string& rssString, const char cQuoteType /*= '\"'*/)
|
||||
{
|
||||
// Iterate through the string
|
||||
std::stringstream sstream;
|
||||
size_t nPos = 0;
|
||||
uint32_t uiUTFChar = 0;
|
||||
while (nPos < rssString.size())
|
||||
{
|
||||
uint8_t uiChar = static_cast<uint8_t>(rssString[nPos]);
|
||||
switch (uiChar)
|
||||
{
|
||||
case '\a': sstream << "\\a"; break;
|
||||
case '\b': sstream << "\\b"; break;
|
||||
case '\f': sstream << "\\f"; break;
|
||||
case '\n': sstream << "\\n"; break;
|
||||
case '\r': sstream << "\\r"; break;
|
||||
case '\t': sstream << "\\t"; break;
|
||||
case '\v': sstream << "\\v"; break;
|
||||
case '\\': sstream << "\\\\"; break;
|
||||
case '\'': if (static_cast<uint8_t>(cQuoteType) == uiChar) sstream << "\\"; sstream << "\'"; break;
|
||||
case '\"': if (static_cast<uint8_t>(cQuoteType) == uiChar) sstream << "\\"; sstream << "\""; break;
|
||||
default:
|
||||
if (uiChar >= 0x20 && uiChar < 0x7f)
|
||||
{
|
||||
// Standard ASCII
|
||||
sstream << static_cast<char>(uiChar);
|
||||
break;
|
||||
}
|
||||
else if (uiChar <= 0x80) // One byte UTF-8
|
||||
uiUTFChar = static_cast<uint32_t>(uiChar);
|
||||
else if (uiChar <= 0xDF) // Two bytes UTF-8
|
||||
{
|
||||
uiUTFChar = static_cast<size_t>(uiChar & 0b00011111) << 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
}
|
||||
else if (uiChar <= 0xEF) // Three bytes UTF-8
|
||||
{
|
||||
uiUTFChar = static_cast<size_t>(uiChar & 0b00001111) << 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
uiUTFChar <<= 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
}
|
||||
else if (uiChar <= 0xF7) // Four bytes UTF-8
|
||||
{
|
||||
uiUTFChar = static_cast<size_t>(uiChar & 0b00000111) << 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
uiUTFChar <<= 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
uiUTFChar <<= 6;
|
||||
|
||||
// Expecting the next character to be between 0x80 and 0xBF
|
||||
nPos++;
|
||||
if (nPos >= rssString.size()) break;
|
||||
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
|
||||
}
|
||||
|
||||
// Stream the UTF character
|
||||
if (uiUTFChar <= 0xFFFF)
|
||||
sstream << "\\u" << std::uppercase << std::hex << std::setfill('0') << std::setw(4) << uiUTFChar;
|
||||
else
|
||||
sstream << "\\U" << std::uppercase << std::hex << std::setfill('0') << std::setw(8) << uiUTFChar;
|
||||
break;
|
||||
}
|
||||
nPos++;
|
||||
}
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
|
||||
CNode::CNode(const std::string& rssName) : m_ssName(rssName)
|
||||
{}
|
||||
|
||||
CNode::~CNode()
|
||||
{}
|
||||
|
||||
sdv::u8string CNode::GetName() const
|
||||
{
|
||||
return m_ssName;
|
||||
}
|
||||
|
||||
sdv::any_t CNode::GetValue() const
|
||||
{
|
||||
return sdv::any_t();
|
||||
}
|
||||
|
||||
sdv::u8string CNode::GetTOML() const
|
||||
{
|
||||
std::string ssLastTable;
|
||||
std::string ssParent;
|
||||
return CreateTOMLText(ssParent, ssLastTable, true, false, true, true);
|
||||
}
|
||||
|
||||
std::shared_ptr<const CArray> CNode::GetArray() const
|
||||
{
|
||||
if (!dynamic_cast<const CArray*>(this)) return {};
|
||||
return std::static_pointer_cast<const CArray>(shared_from_this());
|
||||
}
|
||||
|
||||
std::shared_ptr<CArray> CNode::GetArray()
|
||||
{
|
||||
if (!dynamic_cast<CArray*>(this)) return {};
|
||||
return std::static_pointer_cast<CArray>(shared_from_this());
|
||||
}
|
||||
|
||||
std::shared_ptr<const CTable> CNode::GetTable() const
|
||||
{
|
||||
if (!dynamic_cast<const CTable*>(this)) return {};
|
||||
return std::static_pointer_cast<const CTable>(shared_from_this());
|
||||
}
|
||||
|
||||
std::shared_ptr<CTable> CNode::GetTable()
|
||||
{
|
||||
if (!dynamic_cast<const CTable*>(this)) return {};
|
||||
return std::static_pointer_cast<CTable>(shared_from_this());
|
||||
}
|
||||
|
||||
std::weak_ptr<const CNode> CNode::GetParent() const
|
||||
{
|
||||
return m_ptrParent;
|
||||
}
|
||||
|
||||
void CNode::SetParent(const std::shared_ptr<CNode>& rptrParent)
|
||||
{
|
||||
m_ptrParent = rptrParent;
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CNode::Find(const std::string& /*rssPath*/) const
|
||||
{
|
||||
return std::shared_ptr<CNode>();
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CNode::GetDirect(const std::string& /*rssPath*/) const
|
||||
{
|
||||
// The CNode implementation doesn't have any children. Therefore there is nothing to get.
|
||||
return std::shared_ptr<CNode>();
|
||||
}
|
||||
|
||||
std::string CNode::CreateTOMLText(const std::string& rssParent /*= std::string()*/) const
|
||||
{
|
||||
std::string ssLastTable;
|
||||
return CreateTOMLText(rssParent, ssLastTable);
|
||||
}
|
||||
|
||||
void CNode::Add(const std::string& rssPath, const std::shared_ptr<CNode>& /*rptrNode*/, bool /*bDefinedExplicitly = true*/)
|
||||
{
|
||||
throw XTOMLParseException(("Not allowed to add '" + rssPath + "'; parent node is final").c_str());
|
||||
}
|
||||
|
||||
CBooleanNode::CBooleanNode(const std::string& rssName, bool bVal) : CNode(rssName), m_bVal(bVal)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CBooleanNode::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_boolean;
|
||||
}
|
||||
|
||||
sdv::any_t CBooleanNode::GetValue() const
|
||||
{
|
||||
return sdv::any_t(m_bVal);
|
||||
}
|
||||
|
||||
std::string CBooleanNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool /*bRoot*/) const
|
||||
{
|
||||
std::stringstream sstreamEntry;
|
||||
|
||||
// Do we need to start a table?
|
||||
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
|
||||
{
|
||||
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
|
||||
rssLastPrintedTable = rssParent;
|
||||
}
|
||||
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (!bEmbedded || bAssignment) // Not an array entry
|
||||
sstreamEntry << GetName() << " = ";
|
||||
sstreamEntry << (m_bVal ? "true" : "false");
|
||||
if (!bEmbedded) // Not an array entry
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
CIntegerNode::CIntegerNode(const std::string& rssName, int64_t iVal) : CNode(rssName), m_iVal(iVal)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CIntegerNode::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_integer;
|
||||
}
|
||||
|
||||
sdv::any_t CIntegerNode::GetValue() const
|
||||
{
|
||||
return sdv::any_t(m_iVal);
|
||||
}
|
||||
|
||||
std::string CIntegerNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool /*bRoot*/) const
|
||||
{
|
||||
std::stringstream sstreamEntry;
|
||||
|
||||
// Do we need to start a table?
|
||||
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
|
||||
{
|
||||
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
|
||||
rssLastPrintedTable = rssParent;
|
||||
}
|
||||
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (!bEmbedded || bAssignment) // Not an array entry
|
||||
sstreamEntry << GetName() << " = ";
|
||||
sstreamEntry << m_iVal;
|
||||
if (!bEmbedded) // Not an array entry
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
CFloatingPointNode::CFloatingPointNode(const std::string& rssName, double dVal) : CNode(rssName), m_dVal(dVal)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CFloatingPointNode::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_floating_point;
|
||||
}
|
||||
|
||||
sdv::any_t CFloatingPointNode::GetValue() const
|
||||
{
|
||||
return sdv::any_t(m_dVal);
|
||||
}
|
||||
|
||||
std::string CFloatingPointNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst,
|
||||
bool bEmbedded, bool bAssignment, bool /*bRoot*/) const
|
||||
{
|
||||
std::stringstream sstreamEntry;
|
||||
|
||||
// Do we need to start a table?
|
||||
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
|
||||
{
|
||||
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
|
||||
rssLastPrintedTable = rssParent;
|
||||
}
|
||||
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (!bEmbedded || bAssignment) // Not an array entry
|
||||
sstreamEntry << GetName() << " = ";
|
||||
sstreamEntry << std::setprecision(15) << std::defaultfloat << m_dVal;
|
||||
if (!bEmbedded) // Not an array entry
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
CStringNode::CStringNode(const std::string& rssName, const std::string& rssVal) : CNode(rssName), m_ssVal(rssVal)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CStringNode::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_string;
|
||||
}
|
||||
|
||||
sdv::any_t CStringNode::GetValue() const
|
||||
{
|
||||
return sdv::any_t(m_ssVal);
|
||||
}
|
||||
|
||||
std::string CStringNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool /*bRoot*/) const
|
||||
{
|
||||
std::stringstream sstreamEntry;
|
||||
|
||||
// Do we need to start a table?
|
||||
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
|
||||
{
|
||||
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
|
||||
rssLastPrintedTable = rssParent;
|
||||
}
|
||||
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (!bEmbedded || bAssignment) // Not an array entry
|
||||
sstreamEntry << GetName() << " = ";
|
||||
sstreamEntry << "\"" << EscapeString(m_ssVal) << "\"";
|
||||
if (!bEmbedded) // Not an array entry
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
CNodeCollection::CNodeCollection(const std::string& rssName) : CNode(rssName)
|
||||
{}
|
||||
|
||||
uint32_t CNodeCollection::GetCount() const
|
||||
{
|
||||
return static_cast<uint32_t>(m_vecContent.size());
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CNodeCollection::GetNode(/*in*/ uint32_t uiIndex) const
|
||||
{
|
||||
auto ptrNode = Get(uiIndex);
|
||||
return static_cast<sdv::IInterfaceAccess*>(ptrNode.get());
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CNodeCollection::Get(uint32_t uiIndex) const
|
||||
{
|
||||
if (static_cast<size_t>(uiIndex) >= m_vecContent.size()) return nullptr;
|
||||
|
||||
return m_vecContent[uiIndex];
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CNodeCollection::GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const
|
||||
{
|
||||
auto ptrNode = GetDirect(ssPath);
|
||||
return static_cast<sdv::IInterfaceAccess*>(ptrNode.get());
|
||||
}
|
||||
|
||||
bool CNodeCollection::AddElement(const std::shared_ptr<CNode>& rptrNode, bool bUnique /*= false*/)
|
||||
{
|
||||
if (!rptrNode) return false;
|
||||
if (bUnique && std::find_if(m_vecContent.begin(), m_vecContent.end(), [&](const std::shared_ptr<CNode>& rptrNodeEntry)
|
||||
{
|
||||
return CompareEqual(rptrNodeEntry->GetName(), rptrNode->GetName());
|
||||
}) != m_vecContent.end()) return false;
|
||||
m_vecContent.push_back(rptrNode);
|
||||
return true;
|
||||
}
|
||||
|
||||
CTable::CTable(const std::string& rssName) : CNodeCollection(rssName)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CTable::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_table;
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CTable::GetDirect(const std::string& rssPath) const
|
||||
{
|
||||
size_t nSeparator = FindFirst(rssPath, ".[");
|
||||
std::string ssKey = rssPath.substr(0, nSeparator);
|
||||
std::shared_ptr<CNode> ptrNode;
|
||||
for (uint32_t uiIndex = 0; !ptrNode && uiIndex < GetCount(); uiIndex++)
|
||||
{
|
||||
std::shared_ptr<CNode> ptrNodeEntry = Get(uiIndex);
|
||||
if (!ptrNodeEntry) continue;
|
||||
if (CompareEqual(ptrNodeEntry->GetName(), ssKey)) ptrNode = ptrNodeEntry;
|
||||
}
|
||||
if (!ptrNode) return ptrNode; // Not found
|
||||
|
||||
// Done?
|
||||
if (nSeparator == std::string::npos) return ptrNode;
|
||||
|
||||
// There is more...
|
||||
if (rssPath[nSeparator] == '.') nSeparator++; // Skip dot
|
||||
return ptrNode->GetDirect(rssPath.substr(nSeparator));
|
||||
}
|
||||
|
||||
std::string CTable::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const
|
||||
{
|
||||
// Create the full name
|
||||
std::string ssFullName = rssParent;
|
||||
if (!GetName().empty() && !bRoot)
|
||||
{
|
||||
if (!ssFullName.empty()) ssFullName += ".";
|
||||
ssFullName += GetName();
|
||||
}
|
||||
|
||||
// Stream the table
|
||||
std::stringstream sstreamEntry;
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (bEmbedded) // Embedded table in an array
|
||||
sstreamEntry << "{";
|
||||
for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++)
|
||||
{
|
||||
std::shared_ptr<CNode> ptrNode = Get(uiIndex);
|
||||
if (!ptrNode) continue;
|
||||
sstreamEntry << ptrNode->CreateTOMLText(ssFullName, rssLastPrintedTable, uiIndex == 0, bEmbedded);
|
||||
}
|
||||
if (bEmbedded) // Embedded table in an array
|
||||
sstreamEntry << "}";
|
||||
if (!bEmbedded && !bAssignment && !GetName().empty())
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
void CTable::Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly)
|
||||
{
|
||||
if (!m_bOpenToAddChildren)
|
||||
throw XTOMLParseException(("Not allowed to add '" + rssPath + "'; parent node is final").c_str());
|
||||
|
||||
size_t nDotPos = FindFirst(rssPath);
|
||||
std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
auto ptrParent = Find(ssFirst);
|
||||
|
||||
// Element does not already exist at given path
|
||||
if (!ptrParent)
|
||||
{
|
||||
// Add the new element as a direct child
|
||||
if (nDotPos == std::string::npos)
|
||||
{
|
||||
rptrNode->SetParent(shared_from_this());
|
||||
GetTable()->AddElement(rptrNode);
|
||||
return;
|
||||
}
|
||||
// Add the new element as a descendant further down
|
||||
auto ptrIntermediateTable = std::make_shared<CNormalTable>(ssFirst);
|
||||
ptrIntermediateTable->SetParent(shared_from_this());
|
||||
ptrIntermediateTable->m_bDefinedExplicitly = bDefinedExplicitly;
|
||||
GetTable()->AddElement(ptrIntermediateTable);
|
||||
static_cast<CNode*>(ptrIntermediateTable.get())->Add(ssSecond, rptrNode, bDefinedExplicitly);
|
||||
return;
|
||||
}
|
||||
if (dynamic_cast<CTableArray*>(ptrParent.get()) && nDotPos == std::string::npos)
|
||||
{
|
||||
ptrParent->Add(ssFirst, rptrNode, bDefinedExplicitly);
|
||||
return;
|
||||
}
|
||||
// Element already exists but would be inserted as a direct child
|
||||
if (nDotPos == std::string::npos)
|
||||
{
|
||||
// Make an already implicitly defined table explicitly defined
|
||||
if (ptrParent->GetType() == rptrNode->GetType() && dynamic_cast<CNodeCollection*>(ptrParent.get()) &&
|
||||
!static_cast<CNodeCollection*>(ptrParent.get())->m_bDefinedExplicitly)
|
||||
{
|
||||
static_cast<CNodeCollection*>(ptrParent.get())->m_bDefinedExplicitly = true;
|
||||
return;
|
||||
}
|
||||
throw XTOMLParseException(("Name '" + ssFirst + "' already exists").c_str());
|
||||
}
|
||||
// Element already exists and new element would be added as descendant
|
||||
ptrParent->Add(ssSecond, rptrNode, bDefinedExplicitly);
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CTable::Find(const std::string& rssPath) const
|
||||
{
|
||||
size_t nDotPos = FindFirst(rssPath);
|
||||
std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
|
||||
for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++)
|
||||
{
|
||||
std::shared_ptr<CNode> ptrNode;
|
||||
ptrNode = Get(uiIndex);
|
||||
if (!ptrNode) continue;
|
||||
if (CompareEqual(ptrNode->GetName(), ssFirst))
|
||||
{
|
||||
if (nDotPos == std::string::npos)
|
||||
return ptrNode;
|
||||
return ptrNode->Find(ssSecond);
|
||||
}
|
||||
}
|
||||
|
||||
// No node found...
|
||||
return std::shared_ptr<CNode>();
|
||||
}
|
||||
|
||||
CArray::CArray(const std::string& rssName) : CNodeCollection(rssName)
|
||||
{}
|
||||
|
||||
sdv::toml::ENodeType CArray::GetType() const
|
||||
{
|
||||
return sdv::toml::ENodeType::node_array;
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CArray::GetDirect(const std::string& rssPath) const
|
||||
{
|
||||
size_t nIndexBegin = FindFirst(rssPath, "[");
|
||||
if (nIndexBegin == std::string::npos) return std::shared_ptr<CNode>(); // Unexpected
|
||||
|
||||
size_t nIndexEnd = rssPath.find_first_not_of("0123456789", nIndexBegin + 1);
|
||||
if (nIndexEnd == std::string::npos) return std::shared_ptr<CNode>(); // Unexpected
|
||||
if (rssPath[nIndexEnd] != ']') return std::shared_ptr<CNode>(); // Unexpected
|
||||
std::string ssIndex = rssPath.substr(nIndexBegin + 1, nIndexEnd - nIndexBegin - 1);
|
||||
if (ssIndex.empty()) return std::shared_ptr<CNode>(); // Unexpected
|
||||
uint32_t uiIndex = std::atol(ssIndex.c_str());
|
||||
nIndexEnd++;
|
||||
|
||||
// Get the node
|
||||
if (uiIndex >= GetCount()) return std::shared_ptr<CNode>(); // Not found
|
||||
std::shared_ptr<CNode> ptrNode = Get(uiIndex);
|
||||
|
||||
// Done?
|
||||
if (nIndexEnd == rssPath.size()) return ptrNode;
|
||||
|
||||
// Expecting a dot?
|
||||
size_t nSeparator = nIndexEnd;
|
||||
if (rssPath[nSeparator] == '.') nSeparator++; // Skip dot
|
||||
return ptrNode->GetDirect(rssPath.substr(nSeparator));
|
||||
}
|
||||
|
||||
std::string CArray::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool /*bRoot*/) const
|
||||
{
|
||||
std::stringstream sstreamEntry;
|
||||
|
||||
// Do we need to start a table?
|
||||
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
|
||||
{
|
||||
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
|
||||
rssLastPrintedTable = rssParent;
|
||||
}
|
||||
|
||||
// Stream the array
|
||||
if (bEmbedded && !bFirst) // 2nd or higher array entry
|
||||
sstreamEntry << ", ";
|
||||
if (!bEmbedded || bAssignment) // Not an array entry
|
||||
sstreamEntry << GetName() << " = ";
|
||||
sstreamEntry << "[";
|
||||
for (uint32_t ui = 0; ui < GetCount(); ui++)
|
||||
sstreamEntry << Get(ui)->CreateTOMLText(rssParent, rssLastPrintedTable, ui == 0, true, false);
|
||||
sstreamEntry << "]";
|
||||
if (!bEmbedded) // Not an array entry
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
void CArray::Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly)
|
||||
{
|
||||
size_t nDotPos = FindFirst(rssPath);
|
||||
std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
|
||||
// Add new element to array
|
||||
if (nDotPos == std::string::npos)
|
||||
{
|
||||
GetArray()->AddElement(rptrNode);
|
||||
return;
|
||||
}
|
||||
// Add new element to subelement of array
|
||||
if (std::any_of(ssFirst.begin(), ssFirst.end(), [](char digit) { return (digit < '0') || (digit > '9'); }))
|
||||
{
|
||||
throw XTOMLParseException(("Invalid array access subscript '" + ssFirst + "'").c_str());
|
||||
}
|
||||
uint32_t uiIndex = std::stoi(ssFirst);
|
||||
if (uiIndex >= GetArray()->GetCount())
|
||||
{
|
||||
// This indicates an array within an arrays. Add the intermediate array
|
||||
auto ptrIntermediateArray = std::make_shared<CNormalArray>(ssFirst);
|
||||
ptrIntermediateArray->SetParent(shared_from_this());
|
||||
ptrIntermediateArray->m_bDefinedExplicitly = bDefinedExplicitly;
|
||||
GetArray()->AddElement(ptrIntermediateArray);
|
||||
static_cast<CNode*>(ptrIntermediateArray.get())->Add(ssSecond, rptrNode, bDefinedExplicitly);
|
||||
//throw XTOMLParseException(("Invalid array access index '" + ssFirst + "'; out of bounds").c_str());
|
||||
}
|
||||
GetArray()->Get(uiIndex)->Add(ssSecond, rptrNode, bDefinedExplicitly);
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CArray::Find(const std::string& rssPath) const
|
||||
{
|
||||
size_t nDotPos = FindFirst(rssPath);
|
||||
std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
|
||||
if (ssFirst.empty())
|
||||
throw XTOMLParseException("Missing array subscript");
|
||||
|
||||
if (std::any_of(ssFirst.begin(), ssFirst.end(), [](char digit) { return (digit < '0') || (digit > '9'); }))
|
||||
throw XTOMLParseException(("Invalid array access subscript '" + ssFirst + "'").c_str());
|
||||
uint32_t uiIndex = std::stoi(ssFirst);
|
||||
if (GetArray()->GetCount() <= uiIndex)
|
||||
throw XTOMLParseException(
|
||||
("Invalid array access index '" + ssFirst + "'; out of bounds").c_str());
|
||||
if (nDotPos == std::string::npos)
|
||||
return GetArray()->Get(uiIndex);
|
||||
return GetArray()->Get(uiIndex)->Find(ssSecond);
|
||||
}
|
||||
|
||||
std::string CTableArray::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool /*bFirst*/, bool /*bEmbedded*/,
|
||||
bool /*bAssignment*/, bool bRoot) const
|
||||
{
|
||||
// Create the full name
|
||||
std::string ssFullName = rssParent;
|
||||
if (!ssFullName.empty() && !bRoot) ssFullName += ".";
|
||||
ssFullName += GetName();
|
||||
|
||||
// Stream the array
|
||||
std::stringstream sstreamEntry;
|
||||
for (uint32_t ui = 0; ui < GetCount(); ui++)
|
||||
{
|
||||
sstreamEntry << std::endl << "[[" << ssFullName << "]]" << std::endl;
|
||||
rssLastPrintedTable = ssFullName;
|
||||
sstreamEntry << Get(ui)->CreateTOMLText(ssFullName, rssLastPrintedTable, ui == 0, false, false);
|
||||
}
|
||||
//sstreamEntry << std::endl;
|
||||
|
||||
return sstreamEntry.str();
|
||||
}
|
||||
|
||||
void CTableArray::Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly)
|
||||
{
|
||||
//size_t nDotPos = FindFirst(rssPath);
|
||||
//std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
//std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
|
||||
uint32_t uiSize = GetArray()->GetCount();
|
||||
if (uiSize == 0)
|
||||
{
|
||||
throw XTOMLParseException("Trying to access table in an empty array of tables");
|
||||
}
|
||||
GetArray()->Get(uiSize - 1)->Add(rssPath, rptrNode, bDefinedExplicitly);
|
||||
}
|
||||
|
||||
std::shared_ptr<CNode> CTableArray::Find(const std::string& rssPath) const
|
||||
{
|
||||
//size_t nDotPos = FindFirst(rssPath);
|
||||
//std::string ssFirst = rssPath.substr(0, nDotPos);
|
||||
//std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
|
||||
|
||||
if (!GetArray()->GetCount())
|
||||
throw XTOMLParseException(("Trying to access empty table array; " + rssPath).c_str());
|
||||
return GetArray()->Get(GetArray()->GetCount() - 1)->Find(rssPath);
|
||||
}
|
||||
|
||||
std::string CRootTable::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool /*bFirst*/,
|
||||
bool /*bEmbedded*/, bool /*bAssignment*/, bool /*bRoot*/) const
|
||||
{
|
||||
// Create the full name
|
||||
std::string ssFullName = rssParent;
|
||||
|
||||
// Stream the table
|
||||
std::stringstream sstreamEntry;
|
||||
for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++)
|
||||
{
|
||||
std::shared_ptr<CNode> ptrNode = Get(uiIndex);
|
||||
if (!ptrNode) continue;
|
||||
sstreamEntry << ptrNode->CreateTOMLText(ssFullName, rssLastPrintedTable);
|
||||
}
|
||||
|
||||
sstreamEntry << std::endl;
|
||||
|
||||
// Skip whitespace at the beginning
|
||||
std::string ssRet = sstreamEntry.str();
|
||||
size_t nStart = ssRet.find_first_not_of(" \t\f\r\n\v");
|
||||
if (nStart == std::string::npos) return std::string();
|
||||
return ssRet.substr(nStart);
|
||||
}
|
||||
718
sdv_services/core/toml_parser/parser_node_toml.h
Normal file
718
sdv_services/core/toml_parser/parser_node_toml.h
Normal file
@@ -0,0 +1,718 @@
|
||||
#ifndef PARSER_NODE_TOML_H
|
||||
#define PARSER_NODE_TOML_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <interfaces/toml.h>
|
||||
#include <support/interface_ptr.h>
|
||||
|
||||
// Forward declaration
|
||||
class CArray;
|
||||
class CTable;
|
||||
|
||||
/**
|
||||
* @brief Find the first separator character. Do not include string content (single/or double quoted) and escape characters.
|
||||
* @param[in] rss Reference to the string.
|
||||
* @param[in] rssSeparator One of the characters to find in the string. Must not be an empty string!
|
||||
* @return The position of the first separator character or std::string::npos when none has found.
|
||||
*/
|
||||
size_t FindFirst(const std::string& rss, const std::string& rssSeparator = ".");
|
||||
|
||||
/**
|
||||
* @brief Find the last separator character. Do not include string content (single/or double quoted) and escape characters.
|
||||
* @param[in] rss Reference to the string.
|
||||
* @param[in] rssSeparator One of the characters to find in the string. Must not be an empty string!
|
||||
* @return The position of the last separator character or std::string::npos when none has found.
|
||||
*/
|
||||
size_t FindLast(const std::string& rss, const std::string& rssSeparator = ".");
|
||||
|
||||
/**
|
||||
* @brief Compare both string ignoring the quotes at the first position and last position.
|
||||
* @param[in] rss1 Reference to the first string.
|
||||
* @param[in] rss2 Reference to the second string.
|
||||
* @return The comparison result.
|
||||
*/
|
||||
bool CompareEqual(const std::string& rss1, const std::string& rss2);
|
||||
|
||||
/**
|
||||
* @brief Escape a string using escape characters and UTF values.
|
||||
*/
|
||||
std::string EscapeString(const std::string& rssString, const char cQuoteType = '\"');
|
||||
|
||||
/**
|
||||
* @brief Node to build up the parse tree
|
||||
*/
|
||||
class CNode : public std::enable_shared_from_this<CNode>, public sdv::IInterfaceAccess, public sdv::toml::INodeInfo
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructs a new node object representing a table or an array.
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CNode(const std::string& rssName);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Deleted since Nodes should only be handled via smart-pointer
|
||||
* @{
|
||||
*/
|
||||
CNode(const CNode&) = delete;
|
||||
CNode& operator=(const CNode&) = delete;
|
||||
CNode(const CNode&&) = delete;
|
||||
CNode& operator=(const CNode&&) = delete;
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Destroy the node object
|
||||
*/
|
||||
~CNode();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::toml::INodeInfo)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Get the node name. Overload of sdv::toml::INodeInfo::GetName.
|
||||
* @return String containing the name of the node.
|
||||
*/
|
||||
virtual sdv::u8string GetName() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::any_t GetValue() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetTOML.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::u8string GetTOML() const override;
|
||||
|
||||
/**
|
||||
* @brief Gets the array value of a node
|
||||
* @return Returns a shared pointer of the array value stored in the node if the stored type is array
|
||||
*/
|
||||
std::shared_ptr<const CArray> GetArray() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the array value of a node
|
||||
* @return Returns a shared pointer of the array value stored in the node if the stored type is array
|
||||
*/
|
||||
std::shared_ptr<CArray> GetArray();
|
||||
|
||||
/**
|
||||
* @brief Gets the table value of a node
|
||||
* @return Returns a shared pointer of the table value stored in the node if the stored type is table
|
||||
*/
|
||||
std::shared_ptr<const CTable> GetTable() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the table value of a node
|
||||
* @return Returns a shared pointer of the table value stored in the node if the stored type is table
|
||||
*/
|
||||
std::shared_ptr<CTable> GetTable();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Gets the Parent Node
|
||||
* @return Returns the parent Node
|
||||
* @attention Beware of expiring pointers
|
||||
*/
|
||||
std::weak_ptr<const CNode> GetParent() const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Set the parent node.
|
||||
* @param[in] rptrParent Reference to the node to assign to this node as a parent.
|
||||
*/
|
||||
void SetParent(const std::shared_ptr<CNode>& rptrParent);
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* 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!
|
||||
* @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.
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> GetDirect(const std::string& rssPath) const;
|
||||
|
||||
/**
|
||||
* @brief Create the TOML text based on the content using an optional parent node.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
std::string CreateTOMLText(const std::string& rssParent = std::string()) const;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst = true,
|
||||
bool bEmbedded = false, bool bAssignment = true, bool bRoot = false) const = 0;
|
||||
|
||||
private:
|
||||
std::weak_ptr<CNode> m_ptrParent; ///< Weak pointer to the parent node (if existing).
|
||||
std::string m_ssName; ///< Name of the node.
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Searches the subtree of the node for a node at the given location using the provided path.
|
||||
* @remarks The path elements of arrays and tables are separated by a dot.
|
||||
* @param[in] rssPath Reference to the string containing the path of the node to find.
|
||||
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const;
|
||||
|
||||
/**
|
||||
* @brief Adds a given node to a given path in the tree
|
||||
* @remarks The path elements of arrays and tables are separated by a dot.
|
||||
* @param[in] rssPath Reference to the string containing the path in the tree of the location to the new node to be inserted.
|
||||
* @param[in] rptrNode Reference to the smart pointer containing the new node to be added.
|
||||
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is defined explicitly.
|
||||
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add children.
|
||||
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
|
||||
* be added is already defined explicitly
|
||||
*/
|
||||
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly = true);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Boolean value node.
|
||||
*/
|
||||
class CBooleanNode : public CNode
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the string containing the name of the node.
|
||||
* @param[in] bVal The value to assign.
|
||||
*/
|
||||
CBooleanNode(const std::string& rssName, bool bVal);
|
||||
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::any_t GetValue() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
private:
|
||||
bool m_bVal; ///< Value in case of boolean node.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Integer value node.
|
||||
*/
|
||||
class CIntegerNode : public CNode
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the string containing the name of the node.
|
||||
* @param[in] iVal The value to assign.
|
||||
*/
|
||||
CIntegerNode(const std::string& rssName, int64_t iVal);
|
||||
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::any_t GetValue() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
private:
|
||||
int64_t m_iVal; ///< Value in case of integer node.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Floating point value node.
|
||||
*/
|
||||
class CFloatingPointNode : public CNode
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the string containing the name of the node.
|
||||
* @param[in] dVal The value to assign.
|
||||
*/
|
||||
CFloatingPointNode(const std::string& rssName, double dVal);
|
||||
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::any_t GetValue() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
private:
|
||||
double m_dVal; ///< Value in case of floating point node.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief String value node.
|
||||
*/
|
||||
class CStringNode : public CNode
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the string containing the name of the node.
|
||||
* @param[in] rssVal The value to assign.
|
||||
*/
|
||||
CStringNode(const std::string& rssName, const std::string& rssVal);
|
||||
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
|
||||
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
|
||||
* returns empty.
|
||||
*/
|
||||
virtual sdv::any_t GetValue() const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
private:
|
||||
std::string m_ssVal; ///< Value in case of string or illegal (error) node.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Base structure for arrays and tables.
|
||||
*/
|
||||
class CNodeCollection : public CNode, public sdv::toml::INodeCollection
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CNodeCollection(const std::string& rssName);
|
||||
|
||||
public:
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::toml::INodeCollection)
|
||||
SDV_INTERFACE_CHAIN_BASE(CNode)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @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 sdv::toml::INodeCollection::GetNode.
|
||||
* @param[in] uiIndex Index of the node to get.
|
||||
* @return Interface to the node object.
|
||||
*/
|
||||
virtual IInterfaceAccess* GetNode(/*in*/ uint32_t uiIndex) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the node.
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @brief Searches 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. 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!
|
||||
* @param[in] ssPath The path of the node to searched for.
|
||||
* @return Returns an interface the requested node if available.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const override;
|
||||
|
||||
/**
|
||||
* @brief Add an element to the collection.
|
||||
* @param[in] rptrNode Reference to the node element smart pointer.
|
||||
* @param[in] bUnique When set, check prevents adding an element with the same name.
|
||||
* @return Returns whether the element addition was successful.
|
||||
*/
|
||||
bool AddElement(const std::shared_ptr<CNode>& rptrNode, bool bUnique = false);
|
||||
|
||||
private:
|
||||
|
||||
std::vector<std::shared_ptr<CNode>> m_vecContent; ///< Vector holding the child elements
|
||||
|
||||
public:
|
||||
bool m_bDefinedExplicitly = true; ///< WHen set, the array/table is defined explicitly
|
||||
///< (not internal).
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A dynamic table structure that allows mixed data in form of key value pairs
|
||||
*/
|
||||
class CTable : public CNodeCollection
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CTable(const std::string& rssName);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief Accesses a node by its key in the parse tree. Overload of CNode::GetDirect.
|
||||
* @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!
|
||||
* @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.
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> GetDirect(const std::string& rssPath) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
/**
|
||||
* @brief Adds a given node to a given path in the tree. Overload of CNode::Add.
|
||||
* @param[in] rssPath Reference to the path in the tree where the new node is to be added
|
||||
* @param[in] rptrNode Reference to the smart pointer holding the node.
|
||||
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is
|
||||
* defined explicitly
|
||||
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add
|
||||
* children
|
||||
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
|
||||
* be added is already defined explicitly
|
||||
*/
|
||||
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly) override;
|
||||
|
||||
/**
|
||||
* @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find.
|
||||
* @remarks The path elements of arrays and tables are separated by a dot.
|
||||
* @param[in] rssPath Reference to the path in the tree where the new node is to be added
|
||||
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const override;
|
||||
|
||||
bool m_bOpenToAddChildren = true; ///< If internal table, the table can be extended until the table
|
||||
///< is closed.
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A dynamic array structure that allows mixed data
|
||||
* @details The definition of an array in TOML differentiate massively from the syntax to access the elements. For example an array
|
||||
* in TOML could be defined by:
|
||||
* @code
|
||||
* integers = [ 1, 2, 3 ]
|
||||
* nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ]
|
||||
* [[products]]
|
||||
* name = "Hammer"
|
||||
* sku = 738594937
|
||||
* @endcode
|
||||
* The first two examples define the complete array at once. The third example defines one element to be added to an array. Random
|
||||
* access to previous definitions is not required.
|
||||
* The access functions need random access to each element. The GetDirect function uses the syntax similar to C++:
|
||||
* @code
|
||||
* integers[1] --> gives: 2
|
||||
* nested_mixed_array[1][2] --> gives: "c"
|
||||
* products[0].sku --> gives: 738594937
|
||||
* @endcode
|
||||
* To find array elements, the path names are composed of elements separated by a dot. The Add and Find functions use the following
|
||||
* syntax:
|
||||
* @code
|
||||
* integers.1 --> stores: 2
|
||||
* nested_mixed_array.1.2 --> stores: "c"
|
||||
* products.0.sku --> stores: 738594937
|
||||
* @endcode
|
||||
*/
|
||||
class CArray : public CNodeCollection
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CArray(const std::string& rssName);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
|
||||
* @return Type of the node.
|
||||
*/
|
||||
virtual sdv::toml::ENodeType GetType() const override;
|
||||
|
||||
/**
|
||||
* @brief Accesses a node by its key in the parse tree. Overload of CNode::GetDirect.
|
||||
* @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!
|
||||
* @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.
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> GetDirect(const std::string& rssPath) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
/**
|
||||
* @brief Adds a given node to a given path in the tree. Overload of CNode::Add.
|
||||
* @param[in] rssPath Reference to the path in the tree where the new node is to be added.
|
||||
* @param[in] rptrNode Reference top the smart pointer holding the node.
|
||||
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is
|
||||
* defined explicitly
|
||||
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add
|
||||
* children
|
||||
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
|
||||
* be added is already defined explicitly
|
||||
*/
|
||||
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly) override;
|
||||
|
||||
/**
|
||||
* @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find.
|
||||
* @remarks The path elements of arrays and tables are separated by a dot.
|
||||
* @param[in] rssPath Reference to the path of the node to find.
|
||||
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Normal table
|
||||
*/
|
||||
class CNormalTable : public CTable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CNormalTable(const std::string& rssName) : CTable(rssName) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Inline table
|
||||
*/
|
||||
class CInlineTable : public CTable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CInlineTable(const std::string& rssName) : CTable(rssName) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Normal array
|
||||
*/
|
||||
class CNormalArray : public CArray
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CNormalArray(const std::string& rssName) : CArray(rssName) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Array of tables
|
||||
*/
|
||||
class CTableArray : public CArray
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rssName Reference to the name of the node.
|
||||
*/
|
||||
CTableArray(const std::string& rssName) : CArray(rssName) {}
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
|
||||
/**
|
||||
* @brief Adds a given node to a given path in the tree. Overload of CNode::Add.
|
||||
* @param[in] rssPath Reference to the path in the tree where the new node is to be added.
|
||||
* @param[in] rptrNode Reference top the smart pointer holding the node.
|
||||
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is
|
||||
* defined explicitly
|
||||
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add
|
||||
* children
|
||||
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
|
||||
* be added is already defined explicitly
|
||||
*/
|
||||
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly) override;
|
||||
|
||||
/**
|
||||
* @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find.
|
||||
* @remarks The path elements of arrays and tables are separated by a dot.
|
||||
* @param[in] rssPath Reference to the string containing the path of the node to find.
|
||||
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
|
||||
*/
|
||||
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Root table
|
||||
*/
|
||||
class CRootTable : public CNormalTable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CRootTable() : CNormalTable("root") {}
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
|
||||
* different table was printed in between.
|
||||
* @param[in] bFirst When set, this is the first entry in an array or table.
|
||||
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
|
||||
* @param[in] bAssignment When set, this is a table assignment.
|
||||
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
|
||||
bool bAssignment, bool bRoot) const override;
|
||||
};
|
||||
|
||||
#endif // !defined PARSER_NODE_TOML_H
|
||||
380
sdv_services/core/toml_parser/parser_toml.cpp
Normal file
380
sdv_services/core/toml_parser/parser_toml.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
#include "parser_toml.h"
|
||||
#include <iostream>
|
||||
#include "exception.h"
|
||||
|
||||
CParserTOML::CParserTOML(const std::string& rssString) : m_lexer(rssString)
|
||||
{
|
||||
Process(rssString);
|
||||
}
|
||||
|
||||
void CParserTOML::Clear()
|
||||
{
|
||||
m_ptrRoot = std::make_shared<CRootTable>();
|
||||
m_ssCurrentTable.clear();
|
||||
m_lexer.Reset();
|
||||
while (!m_stackEnvironment.empty()) m_stackEnvironment.pop();
|
||||
}
|
||||
|
||||
bool CParserTOML::Process(/*in*/ const sdv::u8string& ssContent)
|
||||
{
|
||||
Clear();
|
||||
m_lexer.Feed(ssContent);
|
||||
try
|
||||
{
|
||||
// Run through all tokens of the lexer and process the tokens.
|
||||
while (!m_lexer.IsEnd())
|
||||
{
|
||||
CLexerTOML::SToken current = m_lexer.Peek();
|
||||
switch (current.eCategory)
|
||||
{
|
||||
case CLexerTOML::ETokenCategory::token_syntax_table_open:
|
||||
ProcessTable();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_table_array_open:
|
||||
ProcessTableArray();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_key:
|
||||
ProcessValueKey();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_new_line:
|
||||
m_lexer.Consume();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_terminated:
|
||||
case CLexerTOML::ETokenCategory::token_error:
|
||||
throw XTOMLParseException(current.ssContentString);
|
||||
break;
|
||||
default:
|
||||
throw XTOMLParseException("Invalid Syntax; not a Key, Table or Tablearray");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException& e)
|
||||
{
|
||||
std::cout << e.what() << '\n';
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const CNodeCollection& CParserTOML::GetRoot() const
|
||||
{
|
||||
auto ptrCollection = m_ptrRoot->GetTable();
|
||||
return *ptrCollection.get();
|
||||
}
|
||||
|
||||
CNodeCollection& CParserTOML::GetRoot()
|
||||
{
|
||||
auto ptrCollection = m_ptrRoot->GetTable();
|
||||
return *ptrCollection.get();
|
||||
}
|
||||
|
||||
std::string CParserTOML::CreateTOMLText(const std::string& rssParent) const
|
||||
{
|
||||
std::string ssLastPrintedTable;
|
||||
return m_ptrRoot->CreateTOMLText(rssParent, ssLastPrintedTable);
|
||||
}
|
||||
|
||||
bool CParserTOML::Add(const std::string& rssPath, bool bVal)
|
||||
{
|
||||
size_t nOffset = FindLast(rssPath);
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0;
|
||||
else
|
||||
nOffset++;
|
||||
std::string ssName = rssPath.substr(nOffset);
|
||||
|
||||
m_ptrRoot->Add(rssPath, std::make_shared<CBooleanNode>(ssName, bVal));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CParserTOML::Add(const std::string& rssPath, int64_t iVal)
|
||||
{
|
||||
size_t nOffset = FindLast(rssPath);
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0;
|
||||
else
|
||||
nOffset++;
|
||||
std::string ssName = rssPath.substr(nOffset);
|
||||
|
||||
m_ptrRoot->Add(rssPath, std::make_shared<CIntegerNode>(ssName, iVal));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CParserTOML::Add(const std::string& rssPath, double dVal)
|
||||
{
|
||||
size_t nOffset = FindLast(rssPath);
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0;
|
||||
else
|
||||
nOffset++;
|
||||
std::string ssName = rssPath.substr(nOffset);
|
||||
|
||||
m_ptrRoot->Add(rssPath, std::make_shared<CFloatingPointNode>(ssName, dVal));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CParserTOML::Add(const std::string& rssPath, const std::string& rssVal)
|
||||
{
|
||||
size_t nOffset = FindLast(rssPath);
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0;
|
||||
else
|
||||
nOffset++;
|
||||
std::string ssName = rssPath.substr(nOffset);
|
||||
|
||||
m_ptrRoot->Add(rssPath, std::make_shared<CStringNode>(ssName, rssVal));
|
||||
return true;
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessTable()
|
||||
{
|
||||
// Get the table path (table name preceded by parent tables separated with dots).
|
||||
m_lexer.Consume();
|
||||
std::string ssPath = ComposePath();
|
||||
CLexerTOML::SToken sToken = m_lexer.Consume();
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_table_close)
|
||||
{
|
||||
throw XTOMLParseException("invalid Table construct");
|
||||
}
|
||||
|
||||
// Find the last dot - the name follows
|
||||
size_t nOffset = FindLast(ssPath);
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0; // No dot found, the whole path is one table name
|
||||
else
|
||||
nOffset++; // Skip the dot
|
||||
std::string ssName = ssPath.substr(nOffset);
|
||||
|
||||
// Add the table to the root
|
||||
m_ptrRoot->Add(ssPath, std::make_shared<CNormalTable>(ssName), false);
|
||||
|
||||
m_ssCurrentTable = ssPath;
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessTableArray()
|
||||
{
|
||||
m_lexer.Consume();
|
||||
std::string rssKeyPath = ComposePath();
|
||||
auto ptrNode = m_ptrRoot->Find(rssKeyPath);
|
||||
if (!ptrNode)
|
||||
{
|
||||
Add<CTableArray>(rssKeyPath);
|
||||
ptrNode = m_ptrRoot->Find(rssKeyPath);
|
||||
}
|
||||
if (!ptrNode) return;
|
||||
if (dynamic_cast<CTableArray*>(ptrNode.get()))
|
||||
ptrNode->GetArray()->AddElement(std::make_shared<CNormalTable>(""));
|
||||
else
|
||||
throw XTOMLParseException(("'" + rssKeyPath + "' already defined as a non-table-array").c_str());
|
||||
m_ssCurrentTable = rssKeyPath;
|
||||
|
||||
CLexerTOML::SToken sToken = m_lexer.Consume();
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_table_array_close)
|
||||
{
|
||||
throw XTOMLParseException("invalid Table Array construct");
|
||||
}
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessValueKey()
|
||||
{
|
||||
std::string rssKeyPath = (m_ssCurrentTable.empty() ? "" : (m_ssCurrentTable + ".")) + ComposePath();
|
||||
|
||||
CLexerTOML::SToken sToken = m_lexer.Consume();
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_assignment)
|
||||
{
|
||||
throw XTOMLParseException("Assignment expected");
|
||||
}
|
||||
|
||||
ProcessValue(rssKeyPath);
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessValue(const std::string& rssKeyPath)
|
||||
{
|
||||
CLexerTOML::SToken assignmentValue = m_lexer.Consume();
|
||||
switch (assignmentValue.eCategory)
|
||||
{
|
||||
case CLexerTOML::ETokenCategory::token_boolean:
|
||||
Add(rssKeyPath, assignmentValue.bContentBoolean);
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_integer:
|
||||
Add(rssKeyPath, assignmentValue.iContentInteger);
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_float:
|
||||
Add(rssKeyPath, assignmentValue.dContentFloatingpoint);
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_string:
|
||||
Add(rssKeyPath, assignmentValue.ssContentString);
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_array_open:
|
||||
Add<CNormalArray>(rssKeyPath);
|
||||
m_stackEnvironment.push(EEnvironment::env_array);
|
||||
ProcessArray(rssKeyPath);
|
||||
m_stackEnvironment.pop();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_inline_table_open:
|
||||
Add<CInlineTable>(rssKeyPath);
|
||||
m_stackEnvironment.push(EEnvironment::env_inline_table);
|
||||
ProcessInlineTable(rssKeyPath);
|
||||
m_stackEnvironment.pop();
|
||||
break;
|
||||
default:
|
||||
throw XTOMLParseException("Missing value");
|
||||
break;
|
||||
}
|
||||
|
||||
CLexerTOML::SToken sToken = m_lexer.Peek();
|
||||
if (!m_stackEnvironment.empty())
|
||||
{
|
||||
switch (m_stackEnvironment.top())
|
||||
{
|
||||
case EEnvironment::env_array:
|
||||
{
|
||||
int32_t index = 2;
|
||||
while (sToken.eCategory == CLexerTOML::ETokenCategory::token_syntax_new_line)
|
||||
{
|
||||
sToken = m_lexer.Peek(index++);
|
||||
}
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_comma
|
||||
&& sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_array_close)
|
||||
{
|
||||
throw XTOMLParseException(
|
||||
"Invalid Token after value assignment in array; ',' or ']' needed");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EEnvironment::env_inline_table:
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_comma
|
||||
&& sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_inline_table_close)
|
||||
{
|
||||
throw XTOMLParseException(
|
||||
"Invalid Token after value assignment in inline table; ',' or '}' needed ");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_new_line && sToken.eCategory != CLexerTOML::ETokenCategory::token_eof)
|
||||
{
|
||||
throw XTOMLParseException("Invalid Token after value assignment; newline needed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessArray(const std::string& rssKeyPath)
|
||||
{
|
||||
/*
|
||||
Arrays are defined as follow: array_name = [value, value, ...]
|
||||
Arrays can have new-lines between their values.
|
||||
And can end with a comma.
|
||||
For example:
|
||||
integers = [ 1, 2, 3 ]
|
||||
colors = [ "red", "yellow", "green", ]
|
||||
nested_arrays_of_ints = [ [ 1, 2 ],
|
||||
[3, 4, 5] ]
|
||||
nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ]
|
||||
string_array = [ "all", 'strings', """are the same""", '''type''' ]
|
||||
*/
|
||||
|
||||
CLexerTOML::SToken sToken = m_lexer.Peek();
|
||||
|
||||
size_t nIndex = 0;
|
||||
enum class EExpect {value_comma_end, comma_end} eExpect = EExpect::value_comma_end;
|
||||
while (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_array_close)
|
||||
{
|
||||
switch (sToken.eCategory)
|
||||
{
|
||||
//case CLexerTOML::ETokenCategory::token_syntax_array_open: // Embedded array
|
||||
// if (eExpect == comma_end) throw XTOMLParseException("Expecting comma or table end.");
|
||||
// m_lexer.Consume();
|
||||
// ProcessArray(rssKeyPath + "." + std::to_string(nIndex++));
|
||||
// eExpect = comma_end;
|
||||
// break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_new_line:
|
||||
m_lexer.Consume();
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_comma:
|
||||
m_lexer.Consume();
|
||||
eExpect = EExpect::value_comma_end;
|
||||
break;
|
||||
default:
|
||||
if (eExpect == EExpect::comma_end)
|
||||
throw XTOMLParseException("Expecting comma or table end.");
|
||||
ProcessValue(rssKeyPath + "." + std::to_string(nIndex++));
|
||||
eExpect = EExpect::comma_end;
|
||||
break;
|
||||
}
|
||||
sToken = m_lexer.Peek();
|
||||
}
|
||||
m_lexer.Consume();
|
||||
}
|
||||
|
||||
void CParserTOML::ProcessInlineTable(const std::string& rssKeyPath)
|
||||
{
|
||||
/*
|
||||
Inline tables are defined as follow: table_name = {value, value, ...}
|
||||
For example:
|
||||
name = { first = "Tom", last = "Preston-Werner" }
|
||||
point = { x = 1, y = 2 }
|
||||
animal = { type.name = "pug" }
|
||||
*/
|
||||
|
||||
CLexerTOML::SToken sToken = m_lexer.Peek();
|
||||
|
||||
std::string ssCurrentTableTemp = m_ssCurrentTable;
|
||||
m_ssCurrentTable = rssKeyPath;
|
||||
enum class EExpect { value_comma_end, value, comma_end } eExpect = EExpect::value_comma_end;
|
||||
while (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_inline_table_close)
|
||||
{
|
||||
switch (sToken.eCategory)
|
||||
{
|
||||
case CLexerTOML::ETokenCategory::token_syntax_new_line:
|
||||
throw XTOMLParseException("No newlines allowed in inline table");
|
||||
break;
|
||||
case CLexerTOML::ETokenCategory::token_syntax_comma:
|
||||
if (eExpect == EExpect::value)
|
||||
throw XTOMLParseException("Unexpected comma.");
|
||||
m_lexer.Consume();
|
||||
eExpect = EExpect::value;
|
||||
break;
|
||||
default:
|
||||
if (eExpect == EExpect::comma_end)
|
||||
throw XTOMLParseException("Expecting comma or table end.");
|
||||
ProcessValueKey();
|
||||
eExpect = EExpect::comma_end;
|
||||
break;
|
||||
}
|
||||
sToken = m_lexer.Peek();
|
||||
}
|
||||
if (eExpect == EExpect::value)
|
||||
throw XTOMLParseException("Expecting a value before inline table end.");
|
||||
|
||||
m_ptrRoot->Find(rssKeyPath)->GetTable()->m_bOpenToAddChildren = false;
|
||||
|
||||
m_lexer.Consume();
|
||||
m_ssCurrentTable = ssCurrentTableTemp;
|
||||
}
|
||||
|
||||
std::string CParserTOML::ComposePath()
|
||||
{
|
||||
std::string ssPath;
|
||||
CLexerTOML::SToken sToken = m_lexer.Peek();
|
||||
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_dot
|
||||
&& sToken.eCategory != CLexerTOML::ETokenCategory::token_key)
|
||||
throw XTOMLParseException("Invalid Token to assemble path from keys");
|
||||
|
||||
while (sToken.eCategory == CLexerTOML::ETokenCategory::token_syntax_dot
|
||||
|| sToken.eCategory == CLexerTOML::ETokenCategory::token_key)
|
||||
{
|
||||
m_lexer.Consume();
|
||||
if (sToken.eCategory == CLexerTOML::ETokenCategory::token_key)
|
||||
ssPath += sToken.ssContentString;
|
||||
else
|
||||
ssPath += ".";
|
||||
sToken = m_lexer.Peek();
|
||||
}
|
||||
return ssPath;
|
||||
}
|
||||
|
||||
173
sdv_services/core/toml_parser/parser_toml.h
Normal file
173
sdv_services/core/toml_parser/parser_toml.h
Normal file
@@ -0,0 +1,173 @@
|
||||
#ifndef PARSER_TOML_H
|
||||
#define PARSER_TOML_H
|
||||
|
||||
#include "lexer_toml.h"
|
||||
#include "parser_node_toml.h"
|
||||
|
||||
/**
|
||||
* @brief Creates a tree structure from input of UTF-8 encoded TOML source data
|
||||
*/
|
||||
class CParserTOML : public sdv::IInterfaceAccess, public sdv::toml::ITOMLParser
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CParserTOML() = default;
|
||||
|
||||
/**
|
||||
* @brief Construct a new Parser object
|
||||
* @param[in] rssString UTF-8 encoded data of a TOML source
|
||||
*/
|
||||
CParserTOML(const std::string& rssString);
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::toml::ITOMLParser)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_ptrRoot)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Clears the current parse result.
|
||||
* @attention This will render any pointer invalid!
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
// Ignore cppcheck warning for not using dynamic binding when being called through the constructor.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Process the configuration from the supplied content string. Overload of sdv::toml::ITOMLParser.
|
||||
* @param[in] ssContent Configuration string.
|
||||
* @return Returns 'true' when the configuration could be read successfully, false when not.
|
||||
*/
|
||||
virtual bool Process(/*in*/ const sdv::u8string& ssContent) override;
|
||||
|
||||
/**
|
||||
* @{
|
||||
* @brief Return the root node.
|
||||
* @return Reference to the root node collection.
|
||||
*/
|
||||
const CNodeCollection& GetRoot() const;
|
||||
CNodeCollection& GetRoot();
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Get the TOML text based on the content.
|
||||
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
|
||||
* @return The string containing the TOML text.
|
||||
*/
|
||||
std::string CreateTOMLText(const std::string& rssParent = std::string()) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Add a collection node (table or array).
|
||||
* @tparam TCollectionNode The collection node class to add (to create).
|
||||
* @param[in] rssPath Reference to the node path.
|
||||
* @return Returns whether the node could be added.
|
||||
*/
|
||||
template <class TCollectionNode>
|
||||
bool Add(const std::string& rssPath);
|
||||
|
||||
/**
|
||||
* @brief Add a boolean value node.
|
||||
* @param[in] rssPath Reference to the node path.
|
||||
* @param[in] bVal The boolean value.
|
||||
* @return Returns whether the node could be added.
|
||||
*/
|
||||
bool Add(const std::string& rssPath, bool bVal);
|
||||
|
||||
/**
|
||||
* @brief Add a integer value node.
|
||||
* @param[in] rssPath Reference to the node path.
|
||||
* @param[in] iVal The integer value.
|
||||
* @return Returns whether the node could be added.
|
||||
*/
|
||||
bool Add(const std::string& rssPath, int64_t iVal);
|
||||
|
||||
/**
|
||||
* @brief Add a floating point value node.
|
||||
* @param[in] rssPath Reference to the node path.
|
||||
* @param[in] dVal The floating point value.
|
||||
* @return Returns whether the node could be added.
|
||||
*/
|
||||
bool Add(const std::string& rssPath, double dVal);
|
||||
|
||||
/**
|
||||
* @brief Add a string value node.
|
||||
* @param[in] rssPath Reference to the node path.
|
||||
* @param[in] rssVal Reference to the string value.
|
||||
* @return Returns whether the node could be added.
|
||||
*/
|
||||
bool Add(const std::string& rssPath, const std::string& rssVal);
|
||||
|
||||
/**
|
||||
* @brief Process a table declaration.
|
||||
*/
|
||||
void ProcessTable();
|
||||
|
||||
/**
|
||||
* @brief Process a table array declaration.
|
||||
*/
|
||||
void ProcessTableArray();
|
||||
|
||||
/**
|
||||
* @brief Process the value key.
|
||||
*/
|
||||
void ProcessValueKey();
|
||||
|
||||
/**
|
||||
* @brief Process the value with the supplied key.
|
||||
* @param[in] rssKeyPath Reference to the key path string.
|
||||
*/
|
||||
void ProcessValue(const std::string& rssKeyPath);
|
||||
|
||||
/**
|
||||
* @brief Process the array value with the supplied key.
|
||||
* @param[in] rssKeyPath Reference to the key path string.
|
||||
*/
|
||||
void ProcessArray(const std::string& rssKeyPath);
|
||||
|
||||
/**
|
||||
* @brief Process the inline table value with the supplied key.
|
||||
* @param[in] rssKeyPath Reference to the key path string.
|
||||
*/
|
||||
void ProcessInlineTable(const std::string& rssKeyPath);
|
||||
|
||||
/**
|
||||
* @brief Compose a path from lexer tokens. A path is composed of table and array elements separated with a dot.
|
||||
* @return The composed path.
|
||||
*/
|
||||
std::string ComposePath();
|
||||
|
||||
/**
|
||||
* @brief Enum for differentiating between an array environment and an inline table environment for syntax checks.
|
||||
*/
|
||||
enum class EEnvironment
|
||||
{
|
||||
env_array, //!< Environment for an array
|
||||
env_inline_table //!< Environment for a table
|
||||
};
|
||||
std::stack<EEnvironment> m_stackEnvironment; ///< Tracking of environments in nested structures.
|
||||
std::shared_ptr<CNode> m_ptrRoot = std::make_shared<CRootTable>(); ///< The one root node.
|
||||
std::string m_ssCurrentTable; ///< Path to the current table.
|
||||
CLexerTOML m_lexer; ///< Lexer.
|
||||
};
|
||||
|
||||
template <class TCollectionNode>
|
||||
inline bool CParserTOML::Add(const std::string& rssPath)
|
||||
{
|
||||
size_t nOffset = rssPath.rfind('.');
|
||||
if (nOffset == std::string::npos)
|
||||
nOffset = 0;
|
||||
else
|
||||
nOffset++;
|
||||
std::string ssName = rssPath.substr(nOffset);
|
||||
|
||||
m_ptrRoot->Add(rssPath, std::make_shared<TCollectionNode>(ssName), true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // PARSER_TOML_H
|
||||
33
sdv_services/core/toml_parser_util.h
Normal file
33
sdv_services/core/toml_parser_util.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef CONFIG_UTILITY_H
|
||||
#define CONFIG_UTILITY_H
|
||||
|
||||
#include <support/component_impl.h>
|
||||
#include "toml_parser/parser_toml.h"
|
||||
|
||||
/**
|
||||
* @brief Configuration utility component.
|
||||
*/
|
||||
class CTOMLParserUtility : public sdv::CSdvObject
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CTOMLParserUtility() = default;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_parser)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::Utility)
|
||||
DECLARE_OBJECT_CLASS_NAME("TOMLParserUtility")
|
||||
|
||||
private:
|
||||
CParserTOML m_parser; ///< Configuration parser
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT_NO_EXPORT(CTOMLParserUtility)
|
||||
|
||||
#endif // !defined CONFIG_UTILITY_H
|
||||
22
sdv_services/data_dispatch_service/CMakeLists.txt
Normal file
22
sdv_services/data_dispatch_service/CMakeLists.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
# Define project
|
||||
project(sdv_service_datadispatchservice VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(data_dispatch_service SHARED
|
||||
"dispatchservice.cpp"
|
||||
"dispatchservice.h"
|
||||
"transaction.cpp"
|
||||
"transaction.h"
|
||||
"signal.cpp"
|
||||
"signal.h" "trigger.h" "trigger.cpp")
|
||||
target_link_libraries(data_dispatch_service ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_link_options(data_dispatch_service PRIVATE)
|
||||
target_include_directories(data_dispatch_service PRIVATE ./include/)
|
||||
set_target_properties(data_dispatch_service PROPERTIES PREFIX "")
|
||||
set_target_properties(data_dispatch_service PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(data_dispatch_service CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} data_dispatch_service PARENT_SCOPE)
|
||||
215
sdv_services/data_dispatch_service/dispatchservice.cpp
Normal file
215
sdv_services/data_dispatch_service/dispatchservice.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#include "dispatchservice.h"
|
||||
|
||||
|
||||
CDispatchService::CDispatchService()
|
||||
{
|
||||
CreateDirectTransactionID();
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CDispatchService::CreateTxTrigger(uint32_t uiCycleTime, uint32_t uiDelayTime, uint32_t uiBehaviorFlags,
|
||||
sdv::IInterfaceAccess* pTriggerCallback)
|
||||
{
|
||||
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
|
||||
|
||||
// Check for parameter validity
|
||||
if (!uiCycleTime && !(uiBehaviorFlags & static_cast<uint32_t>(sdv::core::ISignalTransmission::ETxTriggerBehavior::spontaneous)))
|
||||
return nullptr;
|
||||
if (!pTriggerCallback)
|
||||
return nullptr;
|
||||
sdv::core::ITxTriggerCallback* pCallback = pTriggerCallback->GetInterface<sdv::core::ITxTriggerCallback>();
|
||||
if (!pCallback) return nullptr;
|
||||
|
||||
std::unique_ptr<CTrigger> ptrTrigger = std::make_unique<CTrigger>(*this, uiCycleTime, uiDelayTime, uiBehaviorFlags, pCallback);
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
|
||||
// exception was triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrTrigger)
|
||||
return nullptr;
|
||||
CTrigger* pObject = ptrTrigger.get();
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
|
||||
// exception was triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!pObject)
|
||||
return nullptr;
|
||||
if (!ptrTrigger->IsValid()) return nullptr;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxTriggers);
|
||||
return m_mapTriggers.emplace(pObject, std::move(ptrTrigger)).second ? pObject : nullptr;
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CDispatchService::RegisterTxSignal(/*in*/ const sdv::u8string& ssSignalName, /*in*/ sdv::any_t anyDefVal)
|
||||
{
|
||||
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
|
||||
auto prSignal = m_mapTxSignals.try_emplace(ssSignalName, *this, ssSignalName, sdv::core::ESignalDirection::sigdir_tx, anyDefVal);
|
||||
return prSignal.first->second.CreateConsumer();
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CDispatchService::RegisterRxSignal(/*in*/ const sdv::u8string& ssSignalName)
|
||||
{
|
||||
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
auto prSignal = m_mapRxSignals.try_emplace(ssSignalName, *this, ssSignalName, sdv::core::ESignalDirection::sigdir_rx);
|
||||
return prSignal.first->second.CreateProvider();
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CDispatchService::RequestSignalPublisher(/*in*/ const sdv::u8string& ssSignalName)
|
||||
{
|
||||
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
auto itSignal = m_mapTxSignals.find(ssSignalName);
|
||||
if (itSignal == m_mapTxSignals.end()) return nullptr;
|
||||
if (itSignal->second.GetDirection() != sdv::core::ESignalDirection::sigdir_tx)
|
||||
return nullptr;
|
||||
return itSignal->second.CreateProvider();
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CDispatchService::AddSignalSubscription(/*in*/ const sdv::u8string& ssSignalName, /*in*/ IInterfaceAccess* pSubscriber)
|
||||
{
|
||||
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
auto itSignal = m_mapRxSignals.find(ssSignalName);
|
||||
if (itSignal == m_mapRxSignals.end()) return 0;
|
||||
if (itSignal->second.GetDirection() != sdv::core::ESignalDirection::sigdir_rx)
|
||||
return 0ull;
|
||||
return itSignal->second.CreateConsumer(pSubscriber);
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::core::SSignalRegistration> CDispatchService::GetRegisteredSignals() const
|
||||
{
|
||||
sdv::sequence<sdv::core::SSignalRegistration> seqRegistrations;
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
for (const auto& prSignal : m_mapRxSignals)
|
||||
seqRegistrations.push_back({prSignal.first, prSignal.second.GetDirection()});
|
||||
for (const auto& prSignal : m_mapTxSignals)
|
||||
seqRegistrations.push_back({prSignal.first, prSignal.second.GetDirection()});
|
||||
return seqRegistrations;
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CDispatchService::CreateTransaction()
|
||||
{
|
||||
// NOTE: Transactions can take place at any time (not only when running). DO not restrict transactions to any
|
||||
// operation mode.
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxTransactions);
|
||||
auto itTransaction = m_lstTransactions.emplace(m_lstTransactions.end(), *this);
|
||||
if (itTransaction == m_lstTransactions.end()) return nullptr;
|
||||
itTransaction->SetIterator(itTransaction);
|
||||
return &(*itTransaction);
|
||||
}
|
||||
|
||||
uint64_t CDispatchService::GetNextTransactionID()
|
||||
{
|
||||
return m_uiTransactionCnt++;
|
||||
}
|
||||
|
||||
void CDispatchService::CreateDirectTransactionID()
|
||||
{
|
||||
m_uiDirectTransactionID = GetNextTransactionID();
|
||||
}
|
||||
|
||||
uint64_t CDispatchService::GetDirectTransactionID() const
|
||||
{
|
||||
return m_uiDirectTransactionID;
|
||||
}
|
||||
|
||||
void CDispatchService::Initialize(const sdv::u8string& /*ssObjectConfig*/)
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::initializing;
|
||||
m_scheduler.Start();
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CDispatchService::GetStatus() const
|
||||
{
|
||||
return m_eObjectStatus;
|
||||
}
|
||||
|
||||
void CDispatchService::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::running || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::configuring;
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::configuring || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::running;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CDispatchService::Shutdown()
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
|
||||
m_scheduler.Stop();
|
||||
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_pending;
|
||||
}
|
||||
|
||||
void CDispatchService::UnregisterSignal(/*in*/ const sdv::u8string& ssSignalName, sdv::core::ESignalDirection eDirection)
|
||||
{
|
||||
// NOTE: Normally the remove function should be called in the configuration mode. Since it doesn't give
|
||||
// feedback and the associated caller might delete any receiving function, allow the removal to take place even
|
||||
// when running.
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
|
||||
// Remove the signal
|
||||
if (eDirection == sdv::core::ESignalDirection::sigdir_rx)
|
||||
m_mapRxSignals.erase(ssSignalName);
|
||||
else
|
||||
m_mapTxSignals.erase(ssSignalName);
|
||||
}
|
||||
|
||||
CSignal* CDispatchService::FindSignal(const sdv::u8string& rssSignalName, sdv::core::ESignalDirection eDirection)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
|
||||
if (eDirection == sdv::core::ESignalDirection::sigdir_rx)
|
||||
{
|
||||
auto itSignal = m_mapRxSignals.find(rssSignalName);
|
||||
return itSignal == m_mapRxSignals.end() ? nullptr : &itSignal->second;
|
||||
} else
|
||||
{
|
||||
auto itSignal = m_mapTxSignals.find(rssSignalName);
|
||||
return itSignal == m_mapTxSignals.end() ? nullptr : &itSignal->second;
|
||||
}
|
||||
}
|
||||
|
||||
void CDispatchService::FinishTransaction(const CTransaction* pTransaction)
|
||||
{
|
||||
// NOTE: Transactions can take place at any time (not only when running). DO not restrict transactions to any
|
||||
// operation mode.
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxTransactions);
|
||||
|
||||
// Delete the transaction
|
||||
auto itTransaction = pTransaction->GetIterator();
|
||||
m_lstTransactions.erase(itTransaction);
|
||||
}
|
||||
|
||||
CScheduler& CDispatchService::GetScheduler()
|
||||
{
|
||||
return m_scheduler;
|
||||
}
|
||||
|
||||
void CDispatchService::RemoveTxTrigger(CTrigger* pTrigger)
|
||||
{
|
||||
// NOTE: Normally the remove function should be called in the configuration mode. Since it doesn't give
|
||||
// feedback and the associated caller might delete any receiving function, allow the removal to take place even
|
||||
// when running.
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxTriggers);
|
||||
m_scheduler.RemoveFromSchedule(pTrigger);
|
||||
m_mapTriggers.erase(pTrigger);
|
||||
}
|
||||
220
sdv_services/data_dispatch_service/dispatchservice.h
Normal file
220
sdv_services/data_dispatch_service/dispatchservice.h
Normal file
@@ -0,0 +1,220 @@
|
||||
#ifndef DISPATCH_SERVICE_H
|
||||
#define DISPATCH_SERVICE_H
|
||||
|
||||
#include <interfaces/dispatch.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <set>
|
||||
|
||||
// Data dispatch service for CAN:
|
||||
//
|
||||
// - CAN Link object for Lotus Eletre registers all the CAN signals as follows:
|
||||
// - Rx Signals are being registered to receive data from the vehicle and supply data to the vehicle devices.
|
||||
// - Signals are all grouped per message and per node/ECU
|
||||
// - One event is sent to update the data in the vehicle devices
|
||||
// - Data is updated first and then the event is broadcasted (toggle buffer)
|
||||
// - Tx Signals are being registered to receive data from the vehicle devices and supply data to the vehicle
|
||||
// - Signals are grouped per message and per node/ECU
|
||||
// - (Default values are available for each signal; either one time reset or reset after each send) -> service task?
|
||||
// - Sending per event or per timer
|
||||
|
||||
#include "transaction.h"
|
||||
#include "signal.h"
|
||||
#include "trigger.h"
|
||||
|
||||
/**
|
||||
* @brief data dispatch service to read/write and react on signal changes
|
||||
*/
|
||||
class CDispatchService : public sdv::CSdvObject, public sdv::core::ISignalTransmission, public sdv::core::ISignalAccess,
|
||||
public sdv::core::IDispatchTransaction, public sdv::IObjectControl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CDispatchService();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ISignalTransmission)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ISignalAccess)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::IDispatchTransaction)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("DataDispatchService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
DECLARE_OBJECT_DEPENDENCIES("TaskTimerService")
|
||||
|
||||
/**
|
||||
* @brief Create a TX trigger object that defines how to trigger the signal transmission. Overload of
|
||||
* sdv::core::ISignalTransmission::CreateTxTrigger.
|
||||
* @param[in] uiCycleTime When set to any value other than 0, provides a cyclic trigger (ms). Could be 0 if cyclic
|
||||
* triggering is not required.
|
||||
* @param[in] uiDelayTime When set to any value other than 0, ensures a minimum time between two triggers. Could be 0
|
||||
* if minimum time should not be enforced.
|
||||
* @param[in] uiBehaviorFlags Zero or more flags from sdv::core::ETxTriggerBehavior.
|
||||
* @param[in] pTriggerCallback Pointer to the trigger callback object. This object needs to expose the ITxTriggerCallback
|
||||
* interface. This interface must stay valid during the lifetime of the generated trigger object.
|
||||
* @returns On success, returns an interface to the trigger object. Use the ITxTrigger interface to assign signals to
|
||||
* the trigger. Returns null when a trigger was requested without cycletime and without trigger behavior (which would
|
||||
* mean it would never trigger). Use IObjectDestroy to destroy the trigger object.
|
||||
*/
|
||||
IInterfaceAccess* CreateTxTrigger(uint32_t uiCycleTime, uint32_t uiDelayTime, uint32_t uiBehaviorFlags,
|
||||
sdv::IInterfaceAccess* pTriggerCallback);
|
||||
|
||||
/**
|
||||
* @brief Register a signal for sending over the network; reading from the dispatch service. Data is provided by the
|
||||
* signal publisher and dependable on the requested behavior stored until it is sent. Overload of
|
||||
* sdv::core::ISignalTransmission::RegisterTxSignal.
|
||||
* @param[in] ssSignalName Name of the signal. To guarantee uniqueness, it is preferred to add the group hierarchy to the
|
||||
* signal name separated by a dot. E.g. with CAN: MAB.BcmChas1Fr03.SteerReCtrlReqAgReq
|
||||
* @param[in] anyDefVal The default value of the signal.
|
||||
* @return Returns the IInterfaceAccess interface that allows access to the ISignalRead interface for reading the
|
||||
* signal value.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* RegisterTxSignal(/*in*/ const sdv::u8string& ssSignalName, /*in*/ sdv::any_t anyDefVal) override;
|
||||
|
||||
/**
|
||||
* @brief Register a signal for reception over the network; providing to the dispatch service. Overload of
|
||||
* sdv::core::ISignalTransmission::RegisterRxSignal.
|
||||
* @param[in] ssSignalName Name of the signal. To guarantee uniqueness, it is preferred to add the group hierarchy to the
|
||||
* signal name separated by a dot. E.g. with CAN: MAB.BcmChas1Fr03.SteerReCtrlReqAgReq
|
||||
* @return Returns the IInterfaceAccess interface that allows access to the ISignalWrite interface for writing the
|
||||
* signal value.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* RegisterRxSignal(/*in*/ const sdv::u8string& ssSignalName) override;
|
||||
|
||||
/**
|
||||
* @brief Requested a registered signal for publication (send signal). Overload of
|
||||
* sdv::core::ISignalAccess::RequestSignalPublisher.
|
||||
* @param[in] ssSignalName Name of the signal. To guarantee uniqueness, it is preferred to add the group hierarchy to the
|
||||
* signal name separated by a dot. E.g. with CAN: MAB.BcmChas1Fr03.SteerReCtrlReqAgReq
|
||||
* @return Returns the IInterfaceAccess interface that allows access to the ISignalWrite interface for writing the
|
||||
* signal value.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* RequestSignalPublisher(/*in*/ const sdv::u8string& ssSignalName) override;
|
||||
|
||||
/**
|
||||
* @brief Add a registered signal for subscription (receive signal). Overload of
|
||||
* sdv::core::ISignalAccess::AddSignalSubscription.
|
||||
* @param[in] ssSignalName Name of the signal. To guarantee uniqueness, it is preferred to add the group hierarchy to the
|
||||
* signal name separated by a dot. E.g. with CAN: MAB.BcmChas1Fr03.SteerReCtrlReqAgReq
|
||||
* @param[in] pSubscriber Pointer to the IInterfaceAccess of the subscriber. The subscriber should implement the
|
||||
* ISignalReceiveEvent interface.
|
||||
* @return Returns an interface that can be used to manage the subscription. Use IObjectDestroy to destroy the signal object.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* AddSignalSubscription(/*in*/ const sdv::u8string& ssSignalName, /*in*/ sdv::IInterfaceAccess* pSubscriber) override;
|
||||
|
||||
/**
|
||||
* @brief Get a list of registered signals.
|
||||
* @return List of registration functions.
|
||||
*/
|
||||
virtual sdv::sequence<sdv::core::SSignalRegistration> GetRegisteredSignals() const override;
|
||||
|
||||
/**
|
||||
* @brief CreateTransaction a transaction. Overload of sdv::core::IDispatchTransaction::CreateTransaction.
|
||||
* @details When starting a group transaction, any writing to a signal will not be reflected yet until the transaction
|
||||
* is finalized. For the data link layer, this also allows freezing the reading values until all values have been read.
|
||||
* @return Returns the transaction interface or NULL when the transaction could not be started. Use IObjectDestroy to
|
||||
* destroy the transaction object.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* CreateTransaction() override;
|
||||
|
||||
/**
|
||||
* @brief Get the next transaction ID.
|
||||
* @return Returns the next transaction ID.
|
||||
*/
|
||||
uint64_t GetNextTransactionID();
|
||||
|
||||
/**
|
||||
* @brief Create a new direct transaction ID.
|
||||
* @details The read transaction ID is used fir direct transmission after a read transaction was started. This prevents direct
|
||||
* transmission overwriting the protected read transaction.
|
||||
*/
|
||||
void CreateDirectTransactionID();
|
||||
|
||||
/**
|
||||
* @brief Get the current direct transaction ID.
|
||||
* @return The transaction ID.
|
||||
*/
|
||||
uint64_t GetDirectTransactionID() const;
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Unregister a previously registered signal. This will render all subscriptions and provider connections invalid.
|
||||
* @param[in] ssSignalName Name of the signal to unregister.
|
||||
* @param[in] eDirection The signal direction determines from which map the signal should be unregistered.
|
||||
*/
|
||||
void UnregisterSignal(const sdv::u8string& ssSignalName, sdv::core::ESignalDirection eDirection);
|
||||
|
||||
/**
|
||||
* @brief Find the signal with the supplied name.
|
||||
* @param[in] rssSignalName Name of the signal to find.
|
||||
* @param[in] eDirection The signal direction determines at which map the signal should be searched for.
|
||||
* @return Pointer to the signal or NULL when the signal could not be found.
|
||||
*/
|
||||
CSignal* FindSignal(const sdv::u8string& rssSignalName, sdv::core::ESignalDirection eDirection);
|
||||
|
||||
/**
|
||||
* @brief Finalize a transaction transaction. Any update made on this interface between the start and the finalize will be
|
||||
* in effect at once.
|
||||
* @param[in] pTransaction The transaction to finalize.
|
||||
*/
|
||||
void FinishTransaction(const CTransaction* pTransaction);
|
||||
|
||||
/**
|
||||
* @brief Get the trigger execution scheduler.
|
||||
* @return Returns a reference to the contained trigger execution scheduler.
|
||||
*/
|
||||
CScheduler& GetScheduler();
|
||||
|
||||
/**
|
||||
* @brief Remove a trigger object from the trigger map.
|
||||
* @param[in] pTrigger Pointer to the trigger object to remove.
|
||||
*/
|
||||
void RemoveTxTrigger(CTrigger* pTrigger);
|
||||
|
||||
private:
|
||||
mutable std::mutex m_mtxSignals; ///< Signal object map protection.
|
||||
std::map<sdv::u8string, CSignal> m_mapRxSignals; ///< Signal object map.
|
||||
std::map<sdv::u8string, CSignal> m_mapTxSignals; ///< Signal object map.
|
||||
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status.
|
||||
std::atomic_uint64_t m_uiTransactionCnt = 1ull; ///< Transaction counter.
|
||||
std::mutex m_mtxTransactions; ///< List with transactions access.
|
||||
std::list<CTransaction> m_lstTransactions; ///< List with transactions.
|
||||
uint64_t m_uiDirectTransactionID; ///< Current direct transaction ID.
|
||||
CScheduler m_scheduler; ///< Scheduler for trigger execution.
|
||||
mutable std::mutex m_mtxTriggers; ///< Trigger object map protection.
|
||||
std::map<CTrigger*, std::unique_ptr<CTrigger>> m_mapTriggers; ///< Trigger object map.
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT(CDispatchService)
|
||||
|
||||
#endif // !defined DISPATCH_SERVICE_H
|
||||
246
sdv_services/data_dispatch_service/signal.cpp
Normal file
246
sdv_services/data_dispatch_service/signal.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
#include "dispatchservice.h"
|
||||
#include "signal.h"
|
||||
#include "trigger.h"
|
||||
#include "transaction.h"
|
||||
|
||||
CProvider::CProvider(CSignal& rSignal) : m_rSignal(rSignal)
|
||||
{}
|
||||
|
||||
void CProvider::DestroyObject()
|
||||
{
|
||||
m_rSignal.RemoveProvider(this);
|
||||
}
|
||||
|
||||
void CProvider::Write(/*in*/ sdv::any_t anyVal, /*in*/ sdv::IInterfaceAccess* pTransaction)
|
||||
{
|
||||
// Let the transaction handle the write if there is a transaction.
|
||||
CTransaction* pTransactionObj = static_cast<CTransaction*>(pTransaction);
|
||||
if (pTransactionObj)
|
||||
{
|
||||
pTransactionObj->DeferWrite(m_rSignal, anyVal);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write the value
|
||||
// Note, explicitly convert to uint64_t to resolve ambiguous function selection under Linux.
|
||||
std::set<CTrigger*> setTriggers;
|
||||
m_rSignal.WriteFromProvider(anyVal, static_cast<uint64_t>(0), setTriggers);
|
||||
|
||||
// Execute the triggers
|
||||
for (CTrigger* pTrigger : setTriggers)
|
||||
pTrigger->Execute();
|
||||
|
||||
}
|
||||
|
||||
CConsumer::CConsumer(CSignal& rSignal, sdv::IInterfaceAccess* pEvent /*= nullptr*/) :
|
||||
m_rSignal(rSignal)
|
||||
{
|
||||
if (pEvent) m_pEvent = pEvent->GetInterface<sdv::core::ISignalReceiveEvent>();
|
||||
}
|
||||
|
||||
void CConsumer::DestroyObject()
|
||||
{
|
||||
m_rSignal.RemoveConsumer(this);
|
||||
}
|
||||
|
||||
sdv::any_t CConsumer::Read(/*in*/ sdv::IInterfaceAccess* pTransaction) const
|
||||
{
|
||||
const CTransaction* pTransactionObj = static_cast<const CTransaction*>(pTransaction);
|
||||
|
||||
// Determine the transaction ID to use
|
||||
uint64_t uiTransactionID = 0;
|
||||
if (pTransactionObj) uiTransactionID = pTransactionObj->GetReadTransactionID();
|
||||
|
||||
// Request a read from the signal
|
||||
return m_rSignal.ReadFromConsumer(uiTransactionID);
|
||||
}
|
||||
|
||||
void CConsumer::Distribute(const sdv::any_t& ranyVal)
|
||||
{
|
||||
if (m_pEvent) m_pEvent->Receive(ranyVal);
|
||||
}
|
||||
|
||||
CSignal::CSignal(CDispatchService& rDispatchSvc, const sdv::u8string& rssName, sdv::core::ESignalDirection eDirection,
|
||||
sdv::any_t anyDefVal /*= sdv::any_t()*/) :
|
||||
m_rDispatchSvc(rDispatchSvc), m_ssName(rssName), m_eDirection(eDirection), m_anyDefVal(anyDefVal)
|
||||
{
|
||||
for (auto& rprVal : m_rgprVal)
|
||||
rprVal = {0, anyDefVal};
|
||||
}
|
||||
|
||||
CSignal::~CSignal()
|
||||
{}
|
||||
|
||||
sdv::u8string CSignal::GetName() const
|
||||
{
|
||||
return m_ssName;
|
||||
}
|
||||
|
||||
sdv::core::ESignalDirection CSignal::GetDirection() const
|
||||
{
|
||||
return m_eDirection;
|
||||
}
|
||||
|
||||
sdv::any_t CSignal::GetDefVal() const
|
||||
{
|
||||
return m_anyDefVal;
|
||||
}
|
||||
|
||||
CProvider* CSignal::CreateProvider()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
|
||||
auto ptrProvider = std::make_unique<CProvider>(*this);
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an exception was
|
||||
// triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrProvider)
|
||||
return nullptr;
|
||||
CProvider* pObject = ptrProvider.get();
|
||||
if (pObject)
|
||||
m_mapProviders.try_emplace(pObject, std::move(ptrProvider));
|
||||
else
|
||||
RemoveProvider(nullptr); // Cleanup
|
||||
return pObject;
|
||||
}
|
||||
|
||||
void CSignal::RemoveProvider(CProvider* pProvider)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
|
||||
m_mapProviders.erase(pProvider);
|
||||
bool bUnregister = m_mapProviders.empty() && m_mapConsumers.empty();
|
||||
lock.unlock();
|
||||
if (bUnregister)
|
||||
m_rDispatchSvc.UnregisterSignal(m_ssName, m_eDirection);
|
||||
}
|
||||
|
||||
CConsumer* CSignal::CreateConsumer(sdv::IInterfaceAccess* pEvent /*= nullptr*/)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
|
||||
auto ptrConsumer = std::make_unique<CConsumer>(*this, pEvent);
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an exception was
|
||||
// triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrConsumer)
|
||||
return nullptr;
|
||||
CConsumer* pObject = ptrConsumer.get();
|
||||
if (pObject)
|
||||
m_mapConsumers.try_emplace(pObject, std::move(ptrConsumer));
|
||||
else
|
||||
RemoveConsumer(nullptr); // Cleanup
|
||||
return pObject;
|
||||
}
|
||||
|
||||
void CSignal::RemoveConsumer(CConsumer* pConsumer)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
|
||||
m_mapConsumers.erase(pConsumer);
|
||||
if (m_mapConsumers.empty())
|
||||
{
|
||||
// Remove any triggers
|
||||
std::unique_lock<std::mutex> lockTrigger(m_mtxTriggers);
|
||||
while (!m_setTriggers.empty())
|
||||
{
|
||||
CTrigger* pTrigger = *m_setTriggers.begin();
|
||||
m_setTriggers.erase(m_setTriggers.begin());
|
||||
lockTrigger.unlock();
|
||||
|
||||
// Remove this signal from the trigger.
|
||||
pTrigger->RemoveSignal(m_ssName);
|
||||
|
||||
lockTrigger.lock();
|
||||
}
|
||||
}
|
||||
|
||||
bool bUnregister = m_mapProviders.empty() && m_mapConsumers.empty();
|
||||
lock.unlock();
|
||||
if (bUnregister)
|
||||
m_rDispatchSvc.UnregisterSignal(m_ssName, m_eDirection);
|
||||
}
|
||||
|
||||
void CSignal::WriteFromProvider(const sdv::any_t& ranyVal, uint64_t uiTransactionID, std::set<CTrigger*>& rsetTriggers)
|
||||
{
|
||||
if (m_rDispatchSvc.GetStatus() != sdv::EObjectStatus::running) return;
|
||||
|
||||
uint64_t uiTransactionIDTemp = uiTransactionID;
|
||||
if (!uiTransactionIDTemp) uiTransactionIDTemp = m_rDispatchSvc.GetDirectTransactionID();
|
||||
|
||||
// Store the value
|
||||
std::unique_lock<std::mutex> lock(m_mtxVal);
|
||||
if (m_nValIndex >= std::extent_v<decltype(m_rgprVal)>) m_nValIndex = 0; // TODO: This is a serious error.
|
||||
size_t nTargetIndex = m_nValIndex;
|
||||
|
||||
// Create an entry with the current transaction ID
|
||||
if (m_rgprVal[nTargetIndex].first < uiTransactionIDTemp)
|
||||
{
|
||||
nTargetIndex = (nTargetIndex + 1) % std::extent_v<decltype(m_rgprVal)>;
|
||||
m_rgprVal[nTargetIndex].first = uiTransactionIDTemp;
|
||||
m_nValIndex = nTargetIndex;
|
||||
}
|
||||
|
||||
// Update the value
|
||||
m_rgprVal[nTargetIndex].second = ranyVal;
|
||||
lock.unlock();
|
||||
|
||||
// Add all triggers to the set of triggers
|
||||
std::unique_lock<std::mutex> lockTriggers(m_mtxTriggers);
|
||||
for (CTrigger* pTrigger : m_setTriggers)
|
||||
rsetTriggers.insert(pTrigger);
|
||||
lockTriggers.unlock();
|
||||
|
||||
// Trigger the update event.
|
||||
DistributeToConsumers(ranyVal);
|
||||
}
|
||||
|
||||
sdv::any_t CSignal::ReadFromConsumer(uint64_t uiTransactionID) const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxVal);
|
||||
|
||||
if (m_nValIndex >= std::extent_v<decltype(m_rgprVal)>) return {}; // TODO: This is a serious error.
|
||||
|
||||
// Determine the transaction ID to use
|
||||
uint64_t uiTransactionIDCopy = uiTransactionID;
|
||||
size_t nTargetIndex = m_nValIndex;
|
||||
if (!uiTransactionIDCopy) uiTransactionIDCopy = m_rgprVal[nTargetIndex].first;
|
||||
|
||||
// Find the signal with the same or lower transaction ID
|
||||
while (m_rgprVal[m_nValIndex].first > uiTransactionIDCopy) // Value too new
|
||||
{
|
||||
// Search for the index with the transaction ID.
|
||||
// Ignore cppcheck comparison operator (template is interpreted as > operator)
|
||||
// cppcheck-suppress compareBoolExpressionWithInt
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
nTargetIndex = (nTargetIndex + std::extent_v<decltype(m_rgprVal)> - 1) % std::extent_v<decltype(m_rgprVal)>;
|
||||
if (nTargetIndex == m_nValIndex) return m_anyDefVal; // Transaction too old... cannot update
|
||||
}
|
||||
|
||||
// Fitting transaction found. Get the value.
|
||||
sdv::any_t anyVal = m_rgprVal[nTargetIndex].second;
|
||||
|
||||
return anyVal;
|
||||
}
|
||||
|
||||
void CSignal::DistributeToConsumers(const sdv::any_t& ranyVal)
|
||||
{
|
||||
if (m_rDispatchSvc.GetStatus() != sdv::EObjectStatus::running) return;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
|
||||
for (auto& rvtConsumer : m_mapConsumers)
|
||||
rvtConsumer.second->Distribute(ranyVal);
|
||||
}
|
||||
|
||||
void CSignal::AddTrigger(CTrigger* pTrigger)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxTriggers);
|
||||
m_setTriggers.emplace(pTrigger);
|
||||
}
|
||||
|
||||
void CSignal::RemoveTrigger(CTrigger* pTrigger)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxTriggers);
|
||||
m_setTriggers.erase(pTrigger);
|
||||
}
|
||||
|
||||
bool CSignal::EqualsDefaultValue() const
|
||||
{
|
||||
return ReadFromConsumer(0) == m_anyDefVal;
|
||||
}
|
||||
238
sdv_services/data_dispatch_service/signal.h
Normal file
238
sdv_services/data_dispatch_service/signal.h
Normal file
@@ -0,0 +1,238 @@
|
||||
#ifndef SIGNAL_H
|
||||
#define SIGNAL_H
|
||||
|
||||
#include <support/interface_ptr.h>
|
||||
#include <interfaces/dispatch.h>
|
||||
#include "trigger.h"
|
||||
|
||||
// Forward declaration
|
||||
class CDispatchService;
|
||||
class CSignal;
|
||||
|
||||
/**
|
||||
* @brief Class implementing the signal provider. Needed for provider interface implementation.
|
||||
*/
|
||||
class CProvider : public sdv::IInterfaceAccess, public sdv::IObjectDestroy, public sdv::core::ISignalWrite
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rSignal Reference to the signal instance.
|
||||
*/
|
||||
explicit CProvider(CSignal& rSignal);
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ISignalWrite)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
|
||||
*/
|
||||
virtual void DestroyObject() override;
|
||||
|
||||
/**
|
||||
* @brief Update the signal value. Overload of sdv::core::ISignalWrite::Write.
|
||||
* @param[in] anyVal The value to update the signal with.
|
||||
* @param[in] pTransaction The transaction interface. Could be NULL in case the update should occur immediately.
|
||||
*/
|
||||
virtual void Write(/*in*/ sdv::any_t anyVal, /*in*/ sdv::IInterfaceAccess* pTransaction) override;
|
||||
|
||||
/**
|
||||
* @brief Update the signal value with the transaction ID supplied. A new entry will be created if the transaction is
|
||||
* larger.
|
||||
* @param[in] ranyVal Reference to the value to update the signal with.
|
||||
* @param[in] uiTransactionID The transaction ID or 0 for current transaction ID.
|
||||
*/
|
||||
void Write(const sdv::any_t& ranyVal, uint64_t uiTransactionID);
|
||||
|
||||
private:
|
||||
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage the module lifetime.
|
||||
CSignal& m_rSignal; ///< Reference to the signal class.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Class implementing the signal consumer. Needed for consumer interface implementation.
|
||||
*/
|
||||
class CConsumer : public sdv::IInterfaceAccess, public sdv::IObjectDestroy, public sdv::core::ISignalRead
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rSignal Reference to the signal instance.
|
||||
* @param[in] pEvent The event to be triggered on a signal change. Optional.
|
||||
*/
|
||||
CConsumer(CSignal& rSignal, sdv::IInterfaceAccess* pEvent = nullptr);
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ISignalRead)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
|
||||
*/
|
||||
virtual void DestroyObject() override;
|
||||
|
||||
/**
|
||||
* @brief Get the signal value. Overload of sdv::core::ISignalRead::Read.
|
||||
* @param[in] pTransaction The transaction interface. Could be NULL in case the most up-to-date value is requested.
|
||||
* @return Returns the value.
|
||||
*/
|
||||
virtual sdv::any_t Read(/*in*/ sdv::IInterfaceAccess* pTransaction) const override;
|
||||
|
||||
/**
|
||||
* @brief Update the signal value with the transaction ID supplied. A new entry will be created if the transaction is
|
||||
* larger.
|
||||
* @param[in] ranyVal Reference to the value to update the signal with.
|
||||
* @param[in] uiTransactionID The transaction ID or 0 for current transaction ID.
|
||||
*/
|
||||
void Write(const sdv::any_t& ranyVal, uint64_t uiTransactionID);
|
||||
|
||||
/**
|
||||
* @brief Distribute a value to all consumers.
|
||||
* @param[in] ranyVal Reference of the value to consumers.
|
||||
*/
|
||||
void Distribute(const sdv::any_t& ranyVal);
|
||||
|
||||
private:
|
||||
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage the module lifetime.
|
||||
CSignal& m_rSignal; ///< Reference to the signal class
|
||||
sdv::core::ISignalReceiveEvent* m_pEvent = nullptr; ///< Receive event interface if available.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Signal administration
|
||||
*/
|
||||
class CSignal
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Helper object to manage object lifetime.
|
||||
*/
|
||||
struct SSignalObjectHelper
|
||||
{
|
||||
/**
|
||||
* @brief Default destructor (needs to be virtual to allow deletion of derived class).
|
||||
*/
|
||||
virtual ~SSignalObjectHelper() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Signal class constructor.
|
||||
* @param[in] rDispatchSvc Reference to the dispatch service.
|
||||
* @param[in] rssName Reference to the name string.
|
||||
* @param[in] anyDefVal Any containing the default value (for send-signals).
|
||||
* @param[in] eDirection Definition whether the signal is a send or receive signal.
|
||||
*/
|
||||
CSignal(CDispatchService& rDispatchSvc, const sdv::u8string& rssName, sdv::core::ESignalDirection eDirection,
|
||||
sdv::any_t anyDefVal = sdv::any_t());
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CSignal();
|
||||
|
||||
/**
|
||||
* @brief Get the name of the signal.
|
||||
* @return String with the signal name.
|
||||
*/
|
||||
sdv::u8string GetName() const;
|
||||
|
||||
/**
|
||||
* @brief Get the signal direction.
|
||||
* @return The signal direction.
|
||||
*/
|
||||
sdv::core::ESignalDirection GetDirection() const;
|
||||
|
||||
/**
|
||||
* @brief Get the signal default value.
|
||||
* @return Any structure with default value.
|
||||
*/
|
||||
sdv::any_t GetDefVal() const;
|
||||
|
||||
/**
|
||||
* @brief Create a provider object.
|
||||
* @return Returns a pointer to the provider object or NULL when the signal is not configured to be a provider.
|
||||
*/
|
||||
CProvider* CreateProvider();
|
||||
|
||||
/**
|
||||
* @brief Remove a provider object.
|
||||
* @remarks If there are no other consumer/provider objects any more, the signal is unregistered from the dispatch service.
|
||||
* @param[in] pProvider Pointer to the provider to remove.
|
||||
*/
|
||||
void RemoveProvider(CProvider* pProvider);
|
||||
|
||||
/**
|
||||
* @brief Create a consumer object.
|
||||
* @param[in] pEvent The event to be triggered on a signal change. Optional.
|
||||
* @return Returns a pointer to the consumer object or NULL when the signal is not configured to be a consumer.
|
||||
*/
|
||||
CConsumer* CreateConsumer(sdv::IInterfaceAccess* pEvent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Remove a consumer object.
|
||||
* @remarks If there are no other consumer/provider objects any more, the signal is unregistered from the dispatch service.
|
||||
* @param[in] pConsumer Pointer to the consumer to remove.
|
||||
*/
|
||||
void RemoveConsumer(CConsumer* pConsumer);
|
||||
|
||||
/**
|
||||
* @brief Update the signal value with the transaction ID supplied. A new entry will be created if the transaction is larger.
|
||||
* @param[in] ranyVal Reference to the value to update the signal with.
|
||||
* @param[in] uiTransactionID The transaction ID or 0 for current transaction ID.
|
||||
* @param[in] rsetTriggers Set of triggers to execute on a spontaneous write.
|
||||
*/
|
||||
void WriteFromProvider(const sdv::any_t& ranyVal, uint64_t uiTransactionID, std::set<CTrigger*>& rsetTriggers);
|
||||
|
||||
/**
|
||||
* @brief Get the signal value.
|
||||
* @param[in] uiTransactionID The transaction ID or 0 for current transaction ID.
|
||||
* @return Returns the value.
|
||||
*/
|
||||
sdv::any_t ReadFromConsumer(uint64_t uiTransactionID) const;
|
||||
|
||||
/**
|
||||
* @brief Distribute a value to all consumers.
|
||||
* @param[in] ranyVal Reference of the value to distribute.
|
||||
*/
|
||||
void DistributeToConsumers(const sdv::any_t& ranyVal);
|
||||
|
||||
/**
|
||||
* @brief Add a trigger to the signal. This trigger will be returned when creating a trigger list.
|
||||
* @param[in] pTrigger Pointer to the trigger object.
|
||||
*/
|
||||
void AddTrigger(CTrigger* pTrigger);
|
||||
|
||||
/**
|
||||
* @brief Remove the trigger from the signal.
|
||||
* @param[in] pTrigger Pointer to the trigger object.
|
||||
*/
|
||||
void RemoveTrigger(CTrigger* pTrigger);
|
||||
|
||||
/**
|
||||
* @brief Returns whether the signal equals the default value.
|
||||
* @return Return the result of the comparison.
|
||||
*/
|
||||
bool EqualsDefaultValue() const;
|
||||
|
||||
private:
|
||||
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage the module lifetime.
|
||||
CDispatchService& m_rDispatchSvc; ///< Reference to dispatch service.
|
||||
sdv::u8string m_ssName; ///< Signal name
|
||||
sdv::core::ESignalDirection m_eDirection = sdv::core::ESignalDirection::sigdir_tx; ///< Signal direction
|
||||
sdv::any_t m_anyDefVal; ///< Default value
|
||||
mutable std::mutex m_mtxVal; ///< Signal value protection
|
||||
mutable std::pair<uint64_t, sdv::any_t> m_rgprVal[16]; ///< The signal value
|
||||
mutable size_t m_nValIndex = 0; ///< Most up-to-date-index
|
||||
mutable std::mutex m_mtxSignalObjects; ///< Signal object map protection.
|
||||
std::map<CProvider*, std::unique_ptr<CProvider>> m_mapProviders; ///< Map with signal objects.
|
||||
std::map<CConsumer*, std::unique_ptr<CConsumer>> m_mapConsumers; ///< Map with signal objects.
|
||||
std::mutex m_mtxTriggers; ///< Protection for the trigger set.
|
||||
std::set<CTrigger*> m_setTriggers; ///< Trigger set for this signal.
|
||||
};
|
||||
|
||||
#endif // !defined SIGNAL_H
|
||||
90
sdv_services/data_dispatch_service/transaction.cpp
Normal file
90
sdv_services/data_dispatch_service/transaction.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "dispatchservice.h"
|
||||
#include "transaction.h"
|
||||
#include "trigger.h"
|
||||
#include "signal.h"
|
||||
|
||||
CTransaction::CTransaction(CDispatchService& rDispatchSvc) :
|
||||
m_rDispatchSvc(rDispatchSvc), m_uiReadTransactionID(rDispatchSvc.GetNextTransactionID())
|
||||
{}
|
||||
|
||||
void CTransaction::DestroyObject()
|
||||
{
|
||||
FinalizeWrite();
|
||||
m_rDispatchSvc.FinishTransaction(this);
|
||||
}
|
||||
|
||||
uint64_t CTransaction::GetReadTransactionID() const
|
||||
{
|
||||
// Set transaction to write if not done so.
|
||||
if (m_eTransactionType != ETransactionType::read_transaction)
|
||||
{
|
||||
if (m_eTransactionType == ETransactionType::undefined)
|
||||
{
|
||||
m_eTransactionType = ETransactionType::read_transaction;
|
||||
|
||||
// Create a new direct transaction ID to prevent the current values from overwriting
|
||||
m_rDispatchSvc.CreateDirectTransactionID();
|
||||
}
|
||||
else
|
||||
return 0ull;
|
||||
}
|
||||
|
||||
return m_uiReadTransactionID;
|
||||
}
|
||||
|
||||
void CTransaction::DeferWrite(CSignal& rSignal, sdv::any_t& ranyVal)
|
||||
{
|
||||
// Set transaction to write if not done so.
|
||||
if (m_eTransactionType != ETransactionType::write_transaction)
|
||||
{
|
||||
if (m_eTransactionType == ETransactionType::undefined)
|
||||
m_eTransactionType = ETransactionType::write_transaction;
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the value to the deferred signal map.
|
||||
std::unique_lock<std::mutex> lock(m_mtxDeferredWriteSignalMap);
|
||||
m_mapDeferredWriteSignalMap.insert_or_assign(&rSignal, ranyVal);
|
||||
}
|
||||
|
||||
void CTransaction::FinalizeWrite()
|
||||
{
|
||||
// Set transaction to write if not done so.
|
||||
if (m_eTransactionType != ETransactionType::write_transaction)
|
||||
{
|
||||
if (m_eTransactionType == ETransactionType::undefined)
|
||||
m_eTransactionType = ETransactionType::write_transaction;
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the next transaction ID
|
||||
uint64_t uiWriteTransaction = m_rDispatchSvc.GetNextTransactionID();
|
||||
|
||||
// Write the signals with the transaction ID.
|
||||
std::unique_lock<std::mutex> lock(m_mtxDeferredWriteSignalMap);
|
||||
std::set<CTrigger*> setTriggers;
|
||||
for (auto& rvtDeferredSignal : m_mapDeferredWriteSignalMap)
|
||||
{
|
||||
if (!rvtDeferredSignal.first) continue;
|
||||
rvtDeferredSignal.first->WriteFromProvider(rvtDeferredSignal.second, uiWriteTransaction, setTriggers);
|
||||
}
|
||||
m_mapDeferredWriteSignalMap.clear();
|
||||
lock.unlock();
|
||||
|
||||
// Execute the triggers
|
||||
for (CTrigger* pTrigger : setTriggers)
|
||||
pTrigger->Execute();
|
||||
}
|
||||
|
||||
void CTransaction::SetIterator(std::list<CTransaction>::iterator itTransaction)
|
||||
{
|
||||
m_itTransaction = itTransaction;
|
||||
}
|
||||
|
||||
std::list<CTransaction>::iterator CTransaction::GetIterator() const
|
||||
{
|
||||
return m_itTransaction;
|
||||
}
|
||||
|
||||
99
sdv_services/data_dispatch_service/transaction.h
Normal file
99
sdv_services/data_dispatch_service/transaction.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#ifndef TRANSACTION_H
|
||||
#define TRANSACTION_H
|
||||
|
||||
#include <support/interface_ptr.h>
|
||||
|
||||
// Forward declaration
|
||||
class CDispatchService;
|
||||
class CSignal;
|
||||
|
||||
/**
|
||||
* @brief Transaction administration
|
||||
* @details During creation of the transaction object, the transaction is of undefined type. The transaction takes the read or
|
||||
* write type only after a call to the read or write function. After this, the transaction doesn't change type any more and will
|
||||
* block calls to the functions provided for the other type (e.g. a read transaction doesn't allow a write and a write transaction
|
||||
* doesn't allow a read).
|
||||
* A read transaction is using the transaction ID to identify the current time of the transaction. Any data available before can
|
||||
* be read. Any data coming after will not be transmitted. In case there is no data any more (the transaction is too old) the
|
||||
* default value will be used.
|
||||
* A write transaction will collect the signal values. The distribution is deferred until the transaction is finalized. During
|
||||
* finalization, a new transaction ID is requested and the values are distributed using this ID (this is necessary so a read
|
||||
* transaction can decide whether the values are included in a read operation).
|
||||
* The written values are stored in a ringbuffer in the signal class. The latest position in the ring buffer contains the last
|
||||
* distributed transaction.
|
||||
*/
|
||||
class CTransaction : public sdv::IInterfaceAccess, public sdv::IObjectDestroy
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rDispatchSvc Reference to the dispatch service.
|
||||
*/
|
||||
explicit CTransaction(CDispatchService& rDispatchSvc);
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IInterfaceAccess)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
|
||||
*/
|
||||
virtual void DestroyObject() override;
|
||||
|
||||
/**
|
||||
* @brief When called, enables the transaction as read-transaction. This would only happen when the transaction is still in
|
||||
* undefined state.
|
||||
* @return Returns the read-transaction ID or 0 when the transaction is not registered as read-transaction.
|
||||
*/
|
||||
uint64_t GetReadTransactionID() const;
|
||||
|
||||
/**
|
||||
* @brief When called, enables the transaction as write-transaction. This would only happen when the transaction is still in
|
||||
* undefined state. In that case, the value will be added to the deferred signal map. Any previous value will be overwritten.
|
||||
* @param[in] rSignal Reference to the signal class.
|
||||
* @param[in] ranyVal Reference to the value.
|
||||
*/
|
||||
void DeferWrite(CSignal& rSignal, sdv::any_t& ranyVal);
|
||||
|
||||
/**
|
||||
* @brief Finalizes the transaction. For read transactions, nothing happens. For write transactions, a transaction ID is stored
|
||||
* with the written signal value.
|
||||
*/
|
||||
void FinalizeWrite();
|
||||
|
||||
/**
|
||||
* @brief Set the iterator to this transaction (prevents having to search for the transaction in the transaction list).
|
||||
* @param[in] itTransaction The iterator to store.
|
||||
*/
|
||||
void SetIterator(std::list<CTransaction>::iterator itTransaction);
|
||||
|
||||
/**
|
||||
* @brief Get the stored iterator to the transaction.
|
||||
* @return The stored iterator.
|
||||
*/
|
||||
std::list<CTransaction>::iterator GetIterator() const;
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* @brief Transaction type
|
||||
*/
|
||||
enum class ETransactionType
|
||||
{
|
||||
undefined = 0, ///< Transaction type was not defined yet.
|
||||
read_transaction = 1, ///< Read transaction (for TX signals).
|
||||
write_transaction = 2, ///< Write transaction (for RX signals and publishers).
|
||||
};
|
||||
|
||||
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage the module lifetime.
|
||||
CDispatchService& m_rDispatchSvc; ///< Reference to dispatch service.
|
||||
mutable ETransactionType m_eTransactionType = ETransactionType::undefined; ///< Transaction type.
|
||||
std::mutex m_mtxDeferredWriteSignalMap; ///< Deferred write signal map access.
|
||||
std::map<CSignal*, sdv::any_t> m_mapDeferredWriteSignalMap; ///< Deferred write signal map.
|
||||
uint64_t m_uiReadTransactionID = 0ull; ///< Read transaction ID.
|
||||
std::list<CTransaction>::iterator m_itTransaction{}; ///< Iterator of the transaction in the transaction list.
|
||||
};
|
||||
|
||||
#endif // !defined TRANSACTION_H
|
||||
205
sdv_services/data_dispatch_service/trigger.cpp
Normal file
205
sdv_services/data_dispatch_service/trigger.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
#include "dispatchservice.h"
|
||||
#include "trigger.h"
|
||||
#include "signal.h"
|
||||
|
||||
CScheduler::~CScheduler()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void CScheduler::Start()
|
||||
{
|
||||
// Start the timer at 1ms rate
|
||||
m_timer = sdv::core::CTaskTimer(1, [&]() { EvaluateAndExecute(); });
|
||||
}
|
||||
|
||||
void CScheduler::Stop()
|
||||
{
|
||||
// Stop the timer
|
||||
m_timer.Reset();
|
||||
|
||||
// Clear the schedule lists
|
||||
std::unique_lock<std::mutex> lock(m_mtxScheduler);
|
||||
m_mapTriggers.clear();
|
||||
m_mmapScheduleList.clear();
|
||||
}
|
||||
|
||||
void CScheduler::Schedule(CTrigger* pTrigger, EExecutionFlag eExecFlag, std::chrono::high_resolution_clock::time_point tpDue)
|
||||
{
|
||||
if (!pTrigger) return;
|
||||
if (!m_timer) return;
|
||||
|
||||
// Check whether a job is scheduled already for this trigger object
|
||||
std::unique_lock<std::mutex> lock(m_mtxScheduler);
|
||||
auto itObject = m_mapTriggers.find(pTrigger);
|
||||
if (itObject != m_mapTriggers.end())
|
||||
{
|
||||
// Trigger execution of the object already found. Update the periodic flag if not set necessary
|
||||
if (eExecFlag == EExecutionFlag::spontaneous)
|
||||
itObject->second = eExecFlag;
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert the object into the scheduled object map.
|
||||
itObject = m_mapTriggers.emplace(pTrigger, eExecFlag).first;
|
||||
if (itObject == m_mapTriggers.end()) return; // Should not happen
|
||||
|
||||
// Schedule the execution
|
||||
m_mmapScheduleList.emplace(tpDue, itObject);
|
||||
}
|
||||
|
||||
void CScheduler::EvaluateAndExecute()
|
||||
{
|
||||
// Run until there is nothing to execute for the moment
|
||||
while (true)
|
||||
{
|
||||
// Check the schedule list
|
||||
std::unique_lock<std::mutex> lock(m_mtxScheduler);
|
||||
auto itJob = m_mmapScheduleList.begin();
|
||||
if (itJob == m_mmapScheduleList.end()) return; // Nothing to do
|
||||
|
||||
// Get the current time and check whether to execute
|
||||
std::chrono::high_resolution_clock::time_point tpNow = std::chrono::high_resolution_clock::now();
|
||||
if (tpNow < itJob->first) return; // No time yet
|
||||
|
||||
// This job is being scheduled; copy the information and remove the entries from the scheduler
|
||||
CTrigger* pTrigger = itJob->second->first;
|
||||
EExecutionFlag eExecFlag = itJob->second->second;
|
||||
m_mapTriggers.erase(itJob->second);
|
||||
m_mmapScheduleList.erase(itJob);
|
||||
lock.unlock();
|
||||
|
||||
// Execute the trigger
|
||||
pTrigger->Execute(eExecFlag);
|
||||
}
|
||||
}
|
||||
|
||||
void CScheduler::RemoveFromSchedule(const CTrigger* pTrigger)
|
||||
{
|
||||
// Search for the job in the scheduled list
|
||||
std::unique_lock<std::mutex> lock(m_mtxScheduler);
|
||||
auto itJob = std::find_if(m_mmapScheduleList.begin(), m_mmapScheduleList.end(),
|
||||
[&](const auto& rvtJob) { return rvtJob.second->first == pTrigger; });
|
||||
if (itJob == m_mmapScheduleList.end()) return; // No scheduled jobs found
|
||||
|
||||
// Remove the job
|
||||
m_mapTriggers.erase(itJob->second);
|
||||
m_mmapScheduleList.erase(itJob);
|
||||
}
|
||||
|
||||
CTrigger::CTrigger(CDispatchService& rDispatchSvc, uint32_t uiCycleTime, uint32_t uiDelayTime, uint32_t uiBehaviorFlags,
|
||||
sdv::core::ITxTriggerCallback* pCallback) :
|
||||
m_rDispatchSvc(rDispatchSvc), m_tpLast{}, m_nPeriod(uiCycleTime), m_nDelay(uiDelayTime), m_uiBehaviorFlags(uiBehaviorFlags),
|
||||
m_pCallback(pCallback)
|
||||
{
|
||||
// Start the timer if this triggr should be perodic
|
||||
if (uiCycleTime)
|
||||
m_timer = sdv::core::CTaskTimer(uiCycleTime, [&]() { Execute(EExecutionFlag::periodic); });
|
||||
}
|
||||
|
||||
bool CTrigger::IsValid() const
|
||||
{
|
||||
if (!m_pCallback) return false;
|
||||
if (m_nPeriod && !m_timer) return false;
|
||||
if (!m_nPeriod && !(m_uiBehaviorFlags & static_cast<uint32_t>(sdv::core::ISignalTransmission::ETxTriggerBehavior::spontaneous)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CTrigger::DestroyObject()
|
||||
{
|
||||
// Stop the timer
|
||||
m_timer.Reset();
|
||||
|
||||
// Remove all signals
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
while (!m_mapSignals.empty())
|
||||
{
|
||||
CSignal* pSignal = m_mapSignals.begin()->second;
|
||||
m_mapSignals.erase(m_mapSignals.begin());
|
||||
if (!pSignal) continue;
|
||||
lock.unlock();
|
||||
|
||||
pSignal->RemoveTrigger(this);
|
||||
|
||||
lock.lock();
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
// Remove the object (this will destruct this object).
|
||||
m_rDispatchSvc.RemoveTxTrigger(this);
|
||||
}
|
||||
|
||||
bool CTrigger::AddSignal(/*in*/ const sdv::u8string& ssSignalName)
|
||||
{
|
||||
CSignal* pSignal = m_rDispatchSvc.FindSignal(ssSignalName, sdv::core::ESignalDirection::sigdir_tx);
|
||||
if (pSignal && pSignal->GetDirection() == sdv::core::ESignalDirection::sigdir_tx)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
pSignal->AddTrigger(this);
|
||||
m_mapSignals.insert(std::make_pair(pSignal->GetName(), pSignal));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CTrigger::RemoveSignal(/*in*/ const sdv::u8string& ssSignalName)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
auto itSignal = m_mapSignals.find(ssSignalName);
|
||||
if (itSignal == m_mapSignals.end()) return;
|
||||
CSignal* pSignal = itSignal->second;
|
||||
m_mapSignals.erase(itSignal);
|
||||
lock.unlock();
|
||||
|
||||
if (pSignal) pSignal->RemoveTrigger(this);
|
||||
}
|
||||
|
||||
void CTrigger::Execute(EExecutionFlag eExecFlag /*= EExecutionFlag::spontaneous*/)
|
||||
{
|
||||
if (m_rDispatchSvc.GetStatus() != sdv::EObjectStatus::running) return;
|
||||
|
||||
// Check for allowed execution
|
||||
if (eExecFlag == EExecutionFlag::spontaneous &&
|
||||
!(m_uiBehaviorFlags & static_cast<uint32_t>(sdv::core::ISignalTransmission::ETxTriggerBehavior::spontaneous)))
|
||||
return;
|
||||
|
||||
// Is there a delay time, check for the delay
|
||||
std::chrono::high_resolution_clock::time_point tpNow = std::chrono::high_resolution_clock::now();
|
||||
if (m_nDelay)
|
||||
{
|
||||
// Calculate earliest execution time
|
||||
std::chrono::high_resolution_clock::time_point tpAllowedExecution = m_tpLast + std::chrono::milliseconds(m_nDelay);
|
||||
|
||||
// Is execution allowed already; if not, schedule execution for later.
|
||||
if (tpNow < tpAllowedExecution)
|
||||
{
|
||||
m_rDispatchSvc.GetScheduler().Schedule(this, eExecFlag, tpAllowedExecution);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for the active flag. If periodic send when active is enabled, check the data for its state and update the repition
|
||||
// counter.
|
||||
if (m_uiBehaviorFlags & static_cast<uint32_t>(sdv::core::ISignalTransmission::ETxTriggerBehavior::periodic_if_active))
|
||||
{
|
||||
// Check whether content is active
|
||||
std::unique_lock<std::mutex> lock(m_mtxSignals);
|
||||
bool bDefault = true;
|
||||
for (const auto& rvtSignal : m_mapSignals)
|
||||
bDefault &= rvtSignal.second->EqualsDefaultValue();
|
||||
|
||||
// Reset or increase repetition counter based on activity
|
||||
m_nInactiveRepetition = bDefault ? m_nInactiveRepetition + 1 : 0;
|
||||
|
||||
// Based on the inactive repitions, decide to execute.
|
||||
// FIXED VALUE: Inactive repitions is set to 1... could be defined dynamic in the future
|
||||
if (eExecFlag == EExecutionFlag::periodic && m_nInactiveRepetition > 1) return;
|
||||
}
|
||||
|
||||
// Execution is allowed, update execution timepoint
|
||||
m_tpLast = tpNow;
|
||||
|
||||
// Execute the trigger
|
||||
if (m_pCallback) m_pCallback->Execute();
|
||||
}
|
||||
155
sdv_services/data_dispatch_service/trigger.h
Normal file
155
sdv_services/data_dispatch_service/trigger.h
Normal file
@@ -0,0 +1,155 @@
|
||||
#ifndef TRIGGER_H
|
||||
#define TRIGGER_H
|
||||
|
||||
#include <support/interface_ptr.h>
|
||||
#include <support/timer.h>
|
||||
|
||||
// Forward declaration
|
||||
class CDispatchService;
|
||||
class CSignal;
|
||||
class CTrigger;
|
||||
|
||||
/**
|
||||
* @brief Flag indicating whether the execution is part of a periodic update or is spontaneous.
|
||||
*/
|
||||
enum class EExecutionFlag
|
||||
{
|
||||
periodic, ///< Execution is triggered periodically
|
||||
spontaneous ///< Spontaneous execution triggered
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Scheduler class being used to schedule a deferred trigger execution when the minimal time between execution is undercut.
|
||||
*/
|
||||
class CScheduler
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CScheduler() = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CScheduler();
|
||||
|
||||
/**
|
||||
* @brief Start the scheduler. This will start the scheduler timer at a 1ms rate.
|
||||
*/
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* @brief Stop the scheduler. This will stop the scheduler timer and clear all pending schedule jobs.
|
||||
*/
|
||||
void Stop();
|
||||
|
||||
/**
|
||||
* @brief Schedule a new trigger execution for the trigger object.
|
||||
* @details If a scheduled trigger execution already exists for the object, the trigger execution is not scheduled again. If
|
||||
* the already scheduled trigger execution was marked as periodic and the new trigger is a spontenous trigger, the scheduled
|
||||
* execution is updated to represent a spontaneous execution (periodic triggers are sometimes not fired when the data hasn't
|
||||
* changed).
|
||||
* @param[in] pTrigger Pointer to the trigger object.
|
||||
* @param[in] eExecFlag Set the trigger execution flag.
|
||||
* @param[in] tpDue The due time this trigger is to be executed.
|
||||
*/
|
||||
void Schedule(CTrigger* pTrigger, EExecutionFlag eExecFlag, std::chrono::high_resolution_clock::time_point tpDue);
|
||||
|
||||
/**
|
||||
* @brief In case the trigger object will be destroyed, remove all pending schedule jobs from the scheduler.
|
||||
* @param[in] pTrigger Pointer to the trigger object.
|
||||
*/
|
||||
void RemoveFromSchedule(const CTrigger* pTrigger);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Evaluate a potential job execution.
|
||||
*/
|
||||
void EvaluateAndExecute();
|
||||
|
||||
/// Map containing the scheduled objects and the execution flag for the object.
|
||||
using CObjectMap = std::map<CTrigger*, EExecutionFlag>;
|
||||
|
||||
/// Multi-map containing the scheduled target time and an iterator to the entry of the scheduler object map.
|
||||
using CSchedulerMMap = std::multimap<std::chrono::high_resolution_clock::time_point, CObjectMap::iterator>;
|
||||
|
||||
sdv::core::CTaskTimer m_timer; ///< 1ms timer
|
||||
std::mutex m_mtxScheduler; ///< Protection of the schedule list
|
||||
CObjectMap m_mapTriggers; ///< Map with the currently scheduled trigger objects (prevents new
|
||||
///< triggers to be scheduled for the same trigger object).
|
||||
CSchedulerMMap m_mmapScheduleList; ///< Schedule lst.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Trigger object, managing the triggering.
|
||||
*/
|
||||
class CTrigger : public sdv::IInterfaceAccess, public sdv::core::ITxTrigger, public sdv::IObjectDestroy
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rDispatchSvc Reference to the dispatch service.
|
||||
* @param[in] uiCycleTime When set to any value other than 0, provides a cyclic trigger (ms). Could be 0 if cyclic
|
||||
* triggering is not required.
|
||||
* @param[in] uiDelayTime When set to any value other than 0, ensures a minimum time between two triggers. Could be 0
|
||||
* if minimum time should not be enforced.
|
||||
* @param[in] uiBehaviorFlags Zero or more flags from sdv::core::ETxTriggerBehavior.
|
||||
* @param[in] pCallback Pointer to the callback interface.
|
||||
*/
|
||||
CTrigger(CDispatchService& rDispatchSvc, uint32_t uiCycleTime, uint32_t uiDelayTime, uint32_t uiBehaviorFlags,
|
||||
sdv::core::ITxTriggerCallback* pCallback);
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IInterfaceAccess)
|
||||
SDV_INTERFACE_ENTRY(sdv::core::ITxTrigger)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Return whether the trigger object is valid.
|
||||
* @return Returns the validity of the trigger.
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
|
||||
*/
|
||||
virtual void DestroyObject() override;
|
||||
|
||||
/**
|
||||
* @brief Add a signal to the trigger object. The signal must be registered as TX signal before.
|
||||
* Overload of sdv::core::ITxTrigger::AddSignal.
|
||||
* @param[in] ssSignalName Name of the signal.
|
||||
* @return Returns whether adding the signal was successful.
|
||||
*/
|
||||
virtual bool AddSignal(/*in*/ const sdv::u8string& ssSignalName) override;
|
||||
|
||||
/**
|
||||
* @brief Remove a signal from the trigger object.Overload of sdv::core::ITxTrigger::RemoveSignal.
|
||||
* @param[in] ssSignalName Name of the signal.
|
||||
*/
|
||||
virtual void RemoveSignal(/*in*/ const sdv::u8string& ssSignalName) override;
|
||||
|
||||
/**
|
||||
* @brief This function is triggered every ms.
|
||||
* @param[in] eExecFlag When set, the trigger was caused by the periodic timer.
|
||||
*/
|
||||
void Execute(EExecutionFlag eExecFlag = EExecutionFlag::spontaneous);
|
||||
|
||||
private:
|
||||
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage the module lifetime.
|
||||
CDispatchService& m_rDispatchSvc; ///< Reference to dispatch service.
|
||||
sdv::core::CTaskTimer m_timer; ///< Task timer event object.
|
||||
std::chrono::high_resolution_clock::time_point m_tpLast; ///< Timepoint of the last trigger
|
||||
size_t m_nPeriod = 0ull; ///< The period to trigger.
|
||||
size_t m_nDelay = 0ull; ///< The minimum delay between two triggers.
|
||||
uint32_t m_uiBehaviorFlags = 0ul; ///< Additional behavior
|
||||
size_t m_nInactiveRepetition = 0ull; ///< Count the amount of inactive executions.
|
||||
sdv::core::ITxTriggerCallback* m_pCallback = nullptr; ///< Callback pointer
|
||||
std::mutex m_mtxSignals; ///< Signal map protection.
|
||||
std::map<sdv::u8string, CSignal*> m_mapSignals; ///< Assigned signals.
|
||||
};
|
||||
|
||||
#endif // ! defined TRIGGER_H
|
||||
23
sdv_services/hardware_ident/CMakeLists.txt
Normal file
23
sdv_services/hardware_ident/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
# Define project
|
||||
project(hardware_ident VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(hardware_ident SHARED
|
||||
"hardware_ident.h"
|
||||
"hardware_ident.cpp")
|
||||
if (WIN32)
|
||||
target_link_libraries(hardware_ident ${CMAKE_THREAD_LIBS_INIT} Rpcrt4.lib)
|
||||
elseif(UNIX)
|
||||
#target_link_libraries(hardware_ident rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_link_libraries(hardware_ident rt ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
target_link_options(hardware_ident PRIVATE)
|
||||
target_include_directories(hardware_ident PRIVATE ./include/)
|
||||
set_target_properties(hardware_ident PROPERTIES PREFIX "")
|
||||
set_target_properties(hardware_ident PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(hardware_ident CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} hardware_ident PARENT_SCOPE)
|
||||
101
sdv_services/hardware_ident/hardware_ident.cpp
Normal file
101
sdv_services/hardware_ident/hardware_ident.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "hardware_ident.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
#pragma push_macro("GetObject")
|
||||
#undef GetObject
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <array>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("GetObject")
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
#ifndef __GNUC__
|
||||
#pragma comment(lib, "Rpcrt4.lib")
|
||||
#endif
|
||||
#elif defined __linux__
|
||||
#include <iostream>
|
||||
//#include <unistd.h>
|
||||
//#include <ifaddrs.h>
|
||||
//#include <netinet/in.h>
|
||||
//#include <arpa/inet.h>
|
||||
//#include <cstdint>
|
||||
//#include <linux/if_packet.h>
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
uint64_t CHardwareIdent::GetHardwareID()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
uint64_t MACAddr = 0;
|
||||
|
||||
UUID uuid;
|
||||
if (UuidCreateSequential(&uuid) == RPC_S_UUID_NO_ADDRESS)
|
||||
{
|
||||
SDV_LOG_ERROR("Error getting UUID!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Converting MAC address to uint64
|
||||
// INFO: Last 6 bytes of the uuid.Data4 contain MAC address and first two bytes contains variant and version,
|
||||
// that's why only last 6 bytes are taken here
|
||||
for (uint32_t ui_it = 2; ui_it < 8; ui_it++)
|
||||
{
|
||||
MACAddr = (MACAddr << 8) | uuid.Data4[ui_it];
|
||||
}
|
||||
|
||||
if (!MACAddr)
|
||||
{
|
||||
SDV_LOG_ERROR("Error getting MAC Address!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return MACAddr;
|
||||
#elif defined __linux__
|
||||
//struct ifaddrs *ifap, *ifa;
|
||||
//uint64_t MACAddr = 0;
|
||||
//if (getifaddrs(&ifap) == -1)
|
||||
//{
|
||||
// SDV_LOG_ERROR("Error getting 'if address'!");
|
||||
// return 0;
|
||||
//}
|
||||
//for (ifa = ifap; ifa != nullptr; ifa = ifa->ifa_next)
|
||||
//{
|
||||
// if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == AF_PACKET)
|
||||
// {
|
||||
// struct sockaddr_ll* scktAddr = (struct sockaddr_ll*)ifa->ifa_addr;
|
||||
// if (scktAddr)
|
||||
// {
|
||||
// // Converting MAC address to uint64
|
||||
// for (uint32_t ui_it = 0; ui_it < 6; ui_it++)
|
||||
// {
|
||||
// MACAddr = (MACAddr << 8) | scktAddr->sll_addr[ui_it];
|
||||
// }
|
||||
// if (MACAddr != 0)
|
||||
// {
|
||||
// freeifaddrs(ifap);
|
||||
// return MACAddr;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//freeifaddrs(ifap);
|
||||
//SDV_LOG_ERROR("Error getting MAC address of the interface!");
|
||||
//return 0;
|
||||
return 0x0102030405060708ll;
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
}
|
||||
43
sdv_services/hardware_ident/hardware_ident.h
Normal file
43
sdv_services/hardware_ident/hardware_ident.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @file process_control.h
|
||||
* @author Sudipta Babu Durjoy FRD DISS21 (mailto:sudipta.durjoy@zf.com)
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2023-10-23
|
||||
*
|
||||
* @copyright Copyright ZF Friedrichshafen AG (c) 2023
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef HARDWARE_IDENT_H
|
||||
#define HARDWARE_IDENT_H
|
||||
|
||||
#include <interfaces/hw_ident.h>
|
||||
#include <support/component_impl.h>
|
||||
|
||||
/**
|
||||
* @brief Hardware ID class
|
||||
*/
|
||||
class CHardwareIdent : public sdv::CSdvObject, public sdv::hardware::IHardwareID
|
||||
{
|
||||
public:
|
||||
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::hardware::IHardwareID)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("HardwareIdentificationService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Gets the hardware ID of the current hardware.
|
||||
* It's same for the all processes running in the same hardware and different for the processes of each different hardwares.
|
||||
* @return Return the hardware ID.
|
||||
*/
|
||||
uint64_t GetHardwareID() override;
|
||||
};
|
||||
DEFINE_SDV_OBJECT(CHardwareIdent)
|
||||
|
||||
#endif // !define HARDWARE_IDENT_H
|
||||
33
sdv_services/ipc_com/CMakeLists.txt
Normal file
33
sdv_services/ipc_com/CMakeLists.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
# Include cross-compilation toolchain file
|
||||
include(../../cross-compile-tools.cmake)
|
||||
|
||||
# Define project
|
||||
project(service_component_isolation VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(ipc_com SHARED
|
||||
"com_ctrl.h"
|
||||
"com_ctrl.cpp"
|
||||
"com_channel.h"
|
||||
"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})
|
||||
else()
|
||||
target_link_libraries(ipc_com ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_link_options(ipc_com PRIVATE)
|
||||
endif()
|
||||
target_include_directories(ipc_com PRIVATE ./include)
|
||||
set_target_properties(ipc_com PROPERTIES PREFIX "")
|
||||
set_target_properties(ipc_com PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(ipc_com CompileCoreIDL)
|
||||
add_dependencies(ipc_com core_ps)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} ipc_com PARENT_SCOPE)
|
||||
321
sdv_services/ipc_com/com_channel.cpp
Normal file
321
sdv_services/ipc_com/com_channel.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
#include "com_channel.h"
|
||||
#include "com_ctrl.h"
|
||||
#include "marshall_object.h"
|
||||
#include <support/pssup.h>
|
||||
#include <support/serdes.h>
|
||||
#include <support/local_service_access.h>
|
||||
#include <interfaces/serdes/core_ps_serdes.h>
|
||||
#include "../../global/scheduler/scheduler.cpp"
|
||||
|
||||
CChannelConnector::CChannelConnector(CCommunicationControl& rcontrol, uint32_t uiIndex, sdv::IInterfaceAccess* pChannelEndpoint) :
|
||||
m_rcontrol(rcontrol), m_ptrChannelEndpoint(pChannelEndpoint),
|
||||
m_pDataSend(m_ptrChannelEndpoint.GetInterface<sdv::ipc::IDataSend>())
|
||||
{
|
||||
m_tConnectionID.uiIdent = uiIndex;
|
||||
m_tConnectionID.uiControl = static_cast<uint32_t>(rand());
|
||||
}
|
||||
|
||||
CChannelConnector::~CChannelConnector()
|
||||
{
|
||||
// Finalize the scheduled calls.
|
||||
m_scheduler.WaitForExecution();
|
||||
|
||||
// Remove all calls from the queue
|
||||
std::unique_lock<std::mutex> lock(m_mtxCalls);
|
||||
while (m_mapCalls.size())
|
||||
{
|
||||
SCallEntry& rsEntry = m_mapCalls.begin()->second;
|
||||
m_mapCalls.erase(m_mapCalls.begin());
|
||||
lock.unlock();
|
||||
|
||||
// Cancel the processing
|
||||
rsEntry.bCancel = true;
|
||||
rsEntry.cvWaitForResult.notify_all();
|
||||
|
||||
// Handle next call.
|
||||
lock.lock();
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
// Disconnect
|
||||
sdv::ipc::IConnect* pConnection = m_ptrChannelEndpoint.GetInterface<sdv::ipc::IConnect>();
|
||||
if (pConnection)
|
||||
{
|
||||
if (m_uiConnectionStatusCookie) pConnection->UnregisterStatusEventCallback(m_uiConnectionStatusCookie);
|
||||
pConnection->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
bool CChannelConnector::ServerConnect(sdv::IInterfaceAccess* pObject, bool bAllowReconnect)
|
||||
{
|
||||
if (!m_ptrChannelEndpoint || !m_pDataSend) return false; // No channel
|
||||
if (!pObject) return false; // No object
|
||||
if (m_ptrInitialMarshallObject) return false; // Has already a marshall object.
|
||||
m_bAllowReconnect = bAllowReconnect;
|
||||
m_eEndpointType = EEndpointType::server;
|
||||
|
||||
m_ptrInitialMarshallObject = m_rcontrol.GetOrCreateStub(pObject);
|
||||
if (!m_ptrInitialMarshallObject) return false;
|
||||
|
||||
// Establish connection...
|
||||
sdv::ipc::IConnect* pConnection = m_ptrChannelEndpoint.GetInterface<sdv::ipc::IConnect>();
|
||||
if (!pConnection) return false;
|
||||
m_uiConnectionStatusCookie = pConnection->RegisterStatusEventCallback(this);
|
||||
return m_uiConnectionStatusCookie != 0 && pConnection->AsyncConnect(this);
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CChannelConnector::ClientConnect(uint32_t uiTimeoutMs)
|
||||
{
|
||||
if (!m_ptrChannelEndpoint || !m_pDataSend) return nullptr; // No channel
|
||||
if (!uiTimeoutMs) return nullptr; // No timeout
|
||||
if (m_ptrInitialMarshallObject) return nullptr; // Has already a marshall object.
|
||||
m_eEndpointType = EEndpointType::client;
|
||||
|
||||
// Get or create the proxy
|
||||
m_ptrInitialMarshallObject = GetOrCreateProxy(sdv::GetInterfaceId<sdv::IInterfaceAccess>(), sdv::ps::TMarshallID{});
|
||||
if (!m_ptrInitialMarshallObject)
|
||||
{
|
||||
SDV_LOG_ERROR("Could not get or create proxy object!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Establish connection...
|
||||
sdv::ipc::IConnect* pConnection = m_ptrChannelEndpoint.GetInterface<sdv::ipc::IConnect>();
|
||||
if (pConnection) m_uiConnectionStatusCookie = pConnection->RegisterStatusEventCallback(this);
|
||||
if (!pConnection || m_uiConnectionStatusCookie == 0 || !pConnection->AsyncConnect(this) ||
|
||||
!pConnection->WaitForConnection(uiTimeoutMs))
|
||||
{
|
||||
SDV_LOG_ERROR("Could not establish a connection!");
|
||||
m_ptrInitialMarshallObject.reset();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_ptrInitialMarshallObject->GetProxy().get<sdv::IInterfaceAccess>();
|
||||
}
|
||||
|
||||
bool CChannelConnector::IsConnected() const
|
||||
{
|
||||
return m_eConnectStatus == sdv::ipc::EConnectStatus::connected;
|
||||
}
|
||||
|
||||
void CChannelConnector::SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus)
|
||||
{
|
||||
auto eConnectStatusTemp = m_eConnectStatus;
|
||||
m_eConnectStatus = eConnectStatus;
|
||||
switch (m_eConnectStatus)
|
||||
{
|
||||
case sdv::ipc::EConnectStatus::disconnected:
|
||||
case sdv::ipc::EConnectStatus::disconnected_forced:
|
||||
// Invalidate the proxy objects.
|
||||
for (auto& rvtProxyObject : m_mapProxyObjects)
|
||||
rvtProxyObject.second.reset();
|
||||
|
||||
if (m_eEndpointType == EEndpointType::server)
|
||||
{
|
||||
// Report information (but only once)
|
||||
if (sdv::app::ConsoleIsVerbose() && eConnectStatusTemp != sdv::ipc::EConnectStatus::disconnected)
|
||||
std::cout << "Client disconnected (ID#" << m_tConnectionID.uiIdent << ")" << std::endl;
|
||||
|
||||
// Remove the connection if reconnection is not enabled (normal case).
|
||||
if (!m_bAllowReconnect)
|
||||
m_rcontrol.RemoveConnection(m_tConnectionID);
|
||||
}
|
||||
break;
|
||||
case sdv::ipc::EConnectStatus::connected:
|
||||
if (m_eEndpointType == EEndpointType::server)
|
||||
{
|
||||
// Report information
|
||||
if (sdv::app::ConsoleIsVerbose())
|
||||
std::cout << "Client connected (ID#" << m_tConnectionID.uiIdent << ")" << std::endl;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CChannelConnector::ReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
|
||||
{
|
||||
// Schedule the data reception
|
||||
std::mutex mtxSyncData;
|
||||
std::condition_variable cvSyncData;
|
||||
bool bSyncData = false;
|
||||
bool bResult = m_scheduler.Schedule([&]()
|
||||
{
|
||||
// Copy the data to keep validity
|
||||
std::unique_lock<std::mutex> lock(mtxSyncData);
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqDataCopy = std::move(seqData);
|
||||
lock.unlock();
|
||||
bSyncData = true;
|
||||
cvSyncData.notify_all();
|
||||
|
||||
// Call the receive function
|
||||
DecoupledReceiveData(seqDataCopy);
|
||||
});
|
||||
std::unique_lock<std::mutex> lockSyncData(mtxSyncData);
|
||||
while (bResult && !bSyncData) cvSyncData.wait_for(lockSyncData, std::chrono::milliseconds(10));
|
||||
lockSyncData.unlock();
|
||||
|
||||
// TODO: Handle a schedule failure - send back a resource depletion.
|
||||
}
|
||||
|
||||
void CChannelConnector::DecoupledReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
|
||||
{
|
||||
// The first data pointer in the sequence contains the address header
|
||||
if (seqData.size() < 1) return;
|
||||
sdv::pointer<uint8_t> rptrAddress = std::move(seqData.front());
|
||||
seqData.erase(seqData.begin());
|
||||
|
||||
// The data should be at least the size of the header.
|
||||
if (rptrAddress.size() < sizeof(sdv::ps::SMarshallAddress)) return; // Invalid size
|
||||
|
||||
// Deserialize the address portion of the header
|
||||
// The first byte in the data pointer determines the endianness
|
||||
sdv::EEndian eSourceEndianess = static_cast<sdv::EEndian>(rptrAddress[0]);
|
||||
|
||||
// Deserialize the address structure
|
||||
sdv::ps::SMarshallAddress sAddress{};
|
||||
if (eSourceEndianess == sdv::EEndian::big_endian)
|
||||
{
|
||||
sdv::deserializer<sdv::EEndian::big_endian> desInput;
|
||||
desInput.attach(rptrAddress, 0); // Do not check checksum...
|
||||
serdes::CSerdes<sdv::ps::SMarshallAddress>::Deserialize(desInput, sAddress);
|
||||
} else
|
||||
{
|
||||
sdv::deserializer<sdv::EEndian::little_endian> desInput;
|
||||
desInput.attach(rptrAddress, 0); // Do not check checksum...
|
||||
serdes::CSerdes<sdv::ps::SMarshallAddress>::Deserialize(desInput, sAddress);
|
||||
}
|
||||
|
||||
// If the data should be interprated as input data, the data should go into a stub object.
|
||||
// If the data should be interprated as output data, the data is returning from the call and should go into the proxy object.
|
||||
if (sAddress.eInterpret == sdv::ps::EMarshallDataInterpret::input_data)
|
||||
{
|
||||
// A proxy ID should be present in any case - if not, this is a serious error, since it is not possible to inform the
|
||||
// caller.
|
||||
if (!sAddress.tProxyID) return;
|
||||
|
||||
// In case the stub ID is not provided, the stub ID is the ID of the initial marshall object
|
||||
sdv::ps::TMarshallID tStubID = sAddress.tStubID;
|
||||
if (!tStubID) tStubID = m_ptrInitialMarshallObject->GetMarshallID();
|
||||
|
||||
// Store the channel context (used to marshall interfaces over the same connector)
|
||||
m_rcontrol.SetConnectorContext(this);
|
||||
|
||||
// Call the stub function
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqResult;
|
||||
try
|
||||
{
|
||||
seqResult = m_rcontrol.CallStub(tStubID, seqData);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Should not occur.
|
||||
std::cout << "Exception occurred..." << std::endl;
|
||||
}
|
||||
|
||||
// Store the address struct into the result sequence
|
||||
sAddress.eInterpret = sdv::ps::EMarshallDataInterpret::output_data;
|
||||
if (eSourceEndianess == sdv::EEndian::big_endian)
|
||||
{
|
||||
sdv::serializer<sdv::EEndian::big_endian> serOutput;
|
||||
serdes::CSerdes<sdv::ps::SMarshallAddress>::Serialize(serOutput, sAddress);
|
||||
seqResult.insert(seqResult.begin(), serOutput.buffer());
|
||||
} else
|
||||
{
|
||||
sdv::serializer<sdv::EEndian::little_endian> serOutput;
|
||||
serdes::CSerdes<sdv::ps::SMarshallAddress>::Serialize(serOutput, sAddress);
|
||||
seqResult.insert(seqResult.begin(), serOutput.buffer());
|
||||
}
|
||||
|
||||
// Send the result back
|
||||
m_pDataSend->SendData(seqResult);
|
||||
} else
|
||||
{
|
||||
// Look for the call entry
|
||||
std::unique_lock<std::mutex> lockCallMap(m_mtxCalls);
|
||||
auto itCall = m_mapCalls.find(sAddress.uiCallIndex);
|
||||
if (itCall == m_mapCalls.end()) return;
|
||||
SCallEntry& rsCallEntry = itCall->second;
|
||||
m_mapCalls.erase(itCall);
|
||||
lockCallMap.unlock();
|
||||
|
||||
// Update the result
|
||||
std::unique_lock<std::mutex> lockCall(rsCallEntry.mtxWaitForResult);
|
||||
rsCallEntry.seqResult = seqData;
|
||||
lockCall.unlock();
|
||||
rsCallEntry.cvWaitForResult.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> CChannelConnector::MakeCall(sdv::ps::TMarshallID tProxyID, sdv::ps::TMarshallID tStubID,
|
||||
sdv::sequence<sdv::pointer<uint8_t>>& rseqInputData)
|
||||
{
|
||||
if (!m_pDataSend) throw sdv::ps::XMarshallNotInitialized();
|
||||
|
||||
// Create an address structure
|
||||
sdv::ps::SMarshallAddress sAddress{};
|
||||
sAddress.eEndian = sdv::GetPlatformEndianess();
|
||||
sAddress.uiVersion = SDVFrameworkInterfaceVersion;
|
||||
sAddress.tProxyID = tProxyID;
|
||||
sAddress.tStubID = tStubID;
|
||||
sAddress.uiCallIndex = m_rcontrol.CreateUniqueCallIndex();
|
||||
sAddress.eInterpret = sdv::ps::EMarshallDataInterpret::input_data;
|
||||
|
||||
// Create an additional stream for the address struct.
|
||||
sdv::serializer serAddress;
|
||||
serdes::CSerdes<sdv::ps::SMarshallAddress>::Serialize(serAddress, sAddress);
|
||||
rseqInputData.insert(rseqInputData.begin(), serAddress.buffer());
|
||||
|
||||
// Add a call entry to be able to receive the result.
|
||||
std::unique_lock<std::mutex> lock(m_mtxCalls);
|
||||
SCallEntry sResult;
|
||||
m_mapCalls.try_emplace(sAddress.uiCallIndex, sResult);
|
||||
lock.unlock();
|
||||
|
||||
// Store the channel context (used to marshall interfaces over the same connector)
|
||||
m_rcontrol.SetConnectorContext(this);
|
||||
|
||||
// Send the data
|
||||
try
|
||||
{
|
||||
if (!m_pDataSend->SendData(rseqInputData)) throw sdv::ps::XMarshallExcept();
|
||||
}
|
||||
catch (const sdv::ps::XMarshallExcept& /*rexcept*/)
|
||||
{
|
||||
// Exception occurred. Remove the call und rethrow.
|
||||
lock.lock();
|
||||
m_mapCalls.erase(sAddress.uiCallIndex);
|
||||
lock.unlock();
|
||||
throw;
|
||||
}
|
||||
|
||||
// Wait for the result
|
||||
if (sResult.bCancel) throw sdv::ps::XMarshallTimeout();
|
||||
std::unique_lock<std::mutex> lockResult(sResult.mtxWaitForResult);
|
||||
sResult.cvWaitForResult.wait(lockResult);
|
||||
if (sResult.bCancel) throw sdv::ps::XMarshallTimeout();
|
||||
|
||||
return sResult.seqResult;
|
||||
}
|
||||
|
||||
std::shared_ptr<CMarshallObject> CChannelConnector::GetOrCreateProxy(sdv::interface_id id, sdv::ps::TMarshallID tStubID)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxMarshallObjects);
|
||||
|
||||
// Check for an existing proxy.
|
||||
auto itMarshallObject = m_mapProxyObjects.find(tStubID);
|
||||
if (itMarshallObject != m_mapProxyObjects.end())
|
||||
return itMarshallObject->second;
|
||||
|
||||
// Proxy doesn't exist; create a new proxy.
|
||||
auto ptrMarshallObject = m_rcontrol.CreateProxy(id, tStubID, *this);
|
||||
if (!ptrMarshallObject)return {};
|
||||
m_mapProxyObjects[tStubID] = ptrMarshallObject;
|
||||
return ptrMarshallObject;
|
||||
}
|
||||
|
||||
sdv::com::TConnectionID CChannelConnector::GetConnectionID() const
|
||||
{
|
||||
return m_tConnectionID;
|
||||
}
|
||||
139
sdv_services/ipc_com/com_channel.h
Normal file
139
sdv_services/ipc_com/com_channel.h
Normal file
@@ -0,0 +1,139 @@
|
||||
#ifndef COM_CHANNEL_H
|
||||
#define COM_CHANNEL_H
|
||||
|
||||
#include <support/pssup.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include "../../global/scheduler/scheduler.h"
|
||||
|
||||
// Forward declaration
|
||||
class CCommunicationControl;
|
||||
class CMarshallObject;
|
||||
|
||||
/**
|
||||
* @brief Communication channel connector (endpoint).
|
||||
*/
|
||||
class CChannelConnector : public sdv::IInterfaceAccess, public sdv::ipc::IConnectEventCallback,
|
||||
public sdv::ipc::IDataReceiveCallback
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for establishing the server connection.
|
||||
* @param[in] rcontrol Reference to the communication control class.
|
||||
* @param[in] uiIndex The current index of this connection (used to create the connection ID).
|
||||
* @param[in] pChannelEndpoint Interface pointer to the channel.
|
||||
*/
|
||||
CChannelConnector(CCommunicationControl& rcontrol, uint32_t uiIndex, sdv::IInterfaceAccess* pChannelEndpoint);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CChannelConnector();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IDataReceiveCallback)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IConnectEventCallback)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Connect as server (attaching the channel to the target object using a stub).
|
||||
* @param[in] pObject Pointer to the object to attach.
|
||||
* @param[in] bAllowReconnect Allow a disconnect and re-connect (disconnect doesn't trigger a channel removal).
|
||||
* @return Returns 'true' when the attachment succeeds; 'false' when not.
|
||||
*/
|
||||
bool ServerConnect(sdv::IInterfaceAccess* pObject, bool bAllowReconnect);
|
||||
|
||||
/**
|
||||
* @brief Connect as client (connecting to an existing server and creating a proxy).
|
||||
* @param[in] uiTimeoutMs The timeout time trying to connect.
|
||||
* @return Returns a pointer to the proxy object representing the object at the other end of the channel. Or NULL when a
|
||||
* timeout occurred.
|
||||
*/
|
||||
sdv::IInterfaceAccess* ClientConnect(uint32_t uiTimeoutMs);
|
||||
|
||||
/**
|
||||
* @brief Is the communication channel currently connected?
|
||||
* @return Returns whether the connector has an active connection.
|
||||
*/
|
||||
bool IsConnected() const;
|
||||
|
||||
/**
|
||||
* @brief Set the current status. Overload of sdv::ipc::IConnectEventCallback::SetStatus.
|
||||
* @param[in] eConnectStatus The connection status.
|
||||
*/
|
||||
virtual void SetStatus(/*in*/ sdv::ipc::EConnectStatus eConnectStatus) override;
|
||||
|
||||
/**
|
||||
* @brief Callback to be called by the IPC connection when receiving a data packet. Overload of
|
||||
* sdv::ipc::IDataReceiveCallback::ReceiveData.
|
||||
* @param[inout] seqData Sequence of data buffers to received. The sequence might be changed to optimize the communication
|
||||
* without having to copy the data.
|
||||
*/
|
||||
virtual void ReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override;
|
||||
|
||||
/**
|
||||
* @brief Decoupled receive callback to be called by the scheduler when receiving a data packet.
|
||||
* @param[inout] seqData Sequence of data buffers to received. The sequence might be changed to optimize the communication
|
||||
* without having to copy the data.
|
||||
*/
|
||||
void DecoupledReceiveData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData);
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @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,
|
||||
sdv::sequence<sdv::pointer<uint8_t>>& rseqInputData);
|
||||
|
||||
/**
|
||||
* @brief Get a proxy for the interface connection to the stub.
|
||||
* @param[in] id The ID of the interface this object marshalls the calls for.
|
||||
* @param[in] tStubID The stub ID this proxy is communicating to.
|
||||
* @return Returns a shared pointer to the proxy object.
|
||||
*/
|
||||
std::shared_ptr<CMarshallObject> GetOrCreateProxy(sdv::interface_id id, sdv::ps::TMarshallID tStubID);
|
||||
|
||||
/**
|
||||
* @brief Get the connection ID of this connector.
|
||||
* @return The connection ID.
|
||||
*/
|
||||
sdv::com::TConnectionID GetConnectionID() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Endpoint type the channel connector
|
||||
*/
|
||||
enum class EEndpointType {server, client};
|
||||
|
||||
/**
|
||||
* @brief Call entry structure that is defined for a call to wait for the result.
|
||||
*/
|
||||
struct SCallEntry
|
||||
{
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqResult; ///< The result data.
|
||||
std::mutex mtxWaitForResult; ///< Mutex to protect result processing.
|
||||
std::condition_variable cvWaitForResult; ///< Condition variable to trigger result processing.
|
||||
bool bCancel = false; ///< Cancel processing when set.
|
||||
};
|
||||
|
||||
CCommunicationControl& m_rcontrol; ///< Reference to the communication control class.
|
||||
sdv::TObjectPtr m_ptrChannelEndpoint; ///< Managed pointer to the channel endpoint.
|
||||
uint64_t m_uiConnectionStatusCookie = 0; ///< Connection status cookie (received after registration).
|
||||
std::shared_ptr<CMarshallObject> m_ptrInitialMarshallObject; ///< Initial marshall object used after a connect event.
|
||||
sdv::ipc::EConnectStatus m_eConnectStatus = sdv::ipc::EConnectStatus::uninitialized; ///< Current connection status.
|
||||
bool m_bAllowReconnect = false; ///< When set, allow reconnection of the server.
|
||||
EEndpointType m_eEndpointType = EEndpointType::client; ///< Endpoint type of this connector.
|
||||
std::recursive_mutex m_mtxMarshallObjects; ///< Synchronize access to the marshall object vector.
|
||||
std::map<sdv::ps::TMarshallID, std::shared_ptr<CMarshallObject>> m_mapProxyObjects; ///< Map of stub IDs to proxy objects
|
||||
sdv::com::TConnectionID m_tConnectionID{}; ///< Connection ID for this connector.
|
||||
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.
|
||||
};
|
||||
|
||||
#endif // !defined COM_CHANNEL_H
|
||||
322
sdv_services/ipc_com/com_ctrl.cpp
Normal file
322
sdv_services/ipc_com/com_ctrl.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
#include "com_ctrl.h"
|
||||
#include <interfaces/ipc.h>
|
||||
#include <support/toml.h>
|
||||
#include "com_channel.h"
|
||||
#include "marshall_object.h"
|
||||
|
||||
thread_local CChannelConnector* CCommunicationControl::m_pConnectorContext = nullptr;
|
||||
|
||||
CCommunicationControl::CCommunicationControl()
|
||||
{
|
||||
std::srand(static_cast<unsigned int>(std::time(nullptr)));
|
||||
}
|
||||
|
||||
CCommunicationControl::~CCommunicationControl()
|
||||
{}
|
||||
|
||||
void CCommunicationControl::Initialize(const sdv::u8string& /*ssObjectConfig*/)
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CCommunicationControl::GetStatus() const
|
||||
{
|
||||
return m_eObjectStatus;
|
||||
}
|
||||
|
||||
void CCommunicationControl::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::running || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::configuring;
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::configuring || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::running;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CCommunicationControl::Shutdown()
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
|
||||
// Wait for threads to terminate... in case one is still running
|
||||
// (use a copy to prevent circular access)
|
||||
std::unique_lock<std::mutex> lock(m_mtxChannels);
|
||||
auto vecInitialConnectMon = std::move(m_vecInitialConnectMon);
|
||||
lock.unlock();
|
||||
for (std::thread& rthread : vecInitialConnectMon)
|
||||
{
|
||||
if (rthread.joinable())
|
||||
rthread.join();
|
||||
}
|
||||
vecInitialConnectMon.clear();
|
||||
|
||||
// Clear the channels and remove the stub objects (use a copy to prevent circular access)
|
||||
lock.lock();
|
||||
auto vecChannels = std::move(m_vecChannels);
|
||||
auto mapStubObjects = std::move(m_mapStubObjects);
|
||||
lock.unlock();
|
||||
vecChannels.clear();
|
||||
mapStubObjects.clear();
|
||||
|
||||
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
|
||||
}
|
||||
|
||||
sdv::com::TConnectionID CCommunicationControl::CreateServerConnection(/*in*/ sdv::com::EChannelType eChannelType,
|
||||
/*in*/ sdv::IInterfaceAccess* pObject, /*in*/ uint32_t uiTimeoutMs, /*out*/ sdv::u8string& ssConnectionString)
|
||||
{
|
||||
std::string ssChannelServer;
|
||||
switch (eChannelType)
|
||||
{
|
||||
case sdv::com::EChannelType::local_channel:
|
||||
ssChannelServer = "LocalChannelControl";
|
||||
break;
|
||||
case sdv::com::EChannelType::remote_channel:
|
||||
ssChannelServer = "RemoteChannelControl";
|
||||
break;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
// Create the channel endpoint
|
||||
sdv::ipc::ICreateEndpoint* pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>(ssChannelServer);
|
||||
if (!pEndpoint)
|
||||
{
|
||||
SDV_LOG_ERROR("No channel control!");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Create a standard endpoint
|
||||
sdv::ipc::SChannelEndpoint sEndpoint = pEndpoint->CreateEndpoint("");
|
||||
if (!sEndpoint.pConnection)
|
||||
{
|
||||
SDV_LOG_ERROR("Could not create the endpoint!");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Manage the channel endpoint object
|
||||
sdv::TObjectPtr ptrChannelEndpoint(sEndpoint.pConnection);
|
||||
|
||||
// Manage the endpoint connection object.
|
||||
sdv::com::TConnectionID tConnectionID = AssignServerEndpoint(ptrChannelEndpoint, pObject, uiTimeoutMs, false);
|
||||
if (!tConnectionID.uiControl) return {};
|
||||
|
||||
ssConnectionString = sEndpoint.ssConnectString;
|
||||
return tConnectionID;
|
||||
}
|
||||
|
||||
sdv::com::TConnectionID CCommunicationControl::CreateClientConnection(/*in*/ const sdv::u8string& ssConnectionString,
|
||||
/*in*/ uint32_t uiTimeoutMs, /*out*/ sdv::IInterfaceAccess*& pProxy)
|
||||
{
|
||||
pProxy = nullptr;
|
||||
|
||||
// The channel connection string contains the nme of the provider
|
||||
sdv::toml::CTOMLParser parser(ssConnectionString);
|
||||
if (!parser.IsValid()) return {};
|
||||
std::string ssProvider = parser.GetDirect("Provider.Name").GetValue();
|
||||
|
||||
// Get the channel access interface
|
||||
sdv::ipc::IChannelAccess* pChannelAccess = sdv::core::GetObject<sdv::ipc::IChannelAccess>(ssProvider);
|
||||
if (!pChannelAccess)
|
||||
{
|
||||
SDV_LOG_ERROR("No channel control!");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Create the endpoint and establish the channel
|
||||
sdv::TObjectPtr ptrChannelEndpoint(pChannelAccess->Access(ssConnectionString));
|
||||
|
||||
return AssignClientEndpoint(ptrChannelEndpoint, uiTimeoutMs, pProxy);
|
||||
}
|
||||
|
||||
sdv::com::TConnectionID CCommunicationControl::AssignServerEndpoint(/*in*/ sdv::IInterfaceAccess* pChannelEndpoint,
|
||||
/*in*/ sdv::IInterfaceAccess* pObject, /*in*/ uint32_t uiTimeoutMs, /*in*/ bool bAllowReconnect)
|
||||
{
|
||||
if (!pChannelEndpoint || !pObject || !(uiTimeoutMs || bAllowReconnect)) return {};
|
||||
|
||||
// Create a communication channel object
|
||||
std::unique_lock<std::mutex> lock(m_mtxChannels);
|
||||
auto ptrCommunication = std::make_shared<CChannelConnector>(*this, static_cast<uint32_t>(m_vecChannels.size()), pChannelEndpoint);
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
|
||||
// exception was triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrCommunication)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to allocate SDV communication channel!");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Start the connection
|
||||
if (!ptrCommunication->ServerConnect(pObject, bAllowReconnect)) return {};
|
||||
|
||||
// Add the channel to the vector
|
||||
m_vecChannels.push_back(ptrCommunication);
|
||||
|
||||
//// If reconnect is not allowed and if not already connected, start the monitor...
|
||||
//if (!bAllowReconnect && !ptrCommunication->IsConnected())
|
||||
// m_vecInitialConnectMon.emplace_back([&]()
|
||||
// {
|
||||
// auto ptrLocalChannel = ptrCommunication;
|
||||
// auto tpStart = std::chrono::high_resolution_clock::now();
|
||||
// while (!ptrLocalChannel->IsConnected())
|
||||
// {
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
// if (std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
// std::chrono::high_resolution_clock::now() - tpStart).count() >
|
||||
// uiTimeoutMs) break;
|
||||
// }
|
||||
|
||||
// if (!ptrLocalChannel->IsConnected())
|
||||
// RemoveChannelConnector(ptrLocalChannel.get());
|
||||
// });
|
||||
|
||||
// Done!
|
||||
return ptrCommunication->GetConnectionID();
|
||||
}
|
||||
|
||||
sdv::com::TConnectionID CCommunicationControl::AssignClientEndpoint(/*in*/ sdv::IInterfaceAccess* pChannelEndpoint,
|
||||
/*in*/ uint32_t uiTimeoutMs, /*out*/ sdv::IInterfaceAccess*& pProxy)
|
||||
{
|
||||
pProxy = nullptr;
|
||||
|
||||
// Create a communication channel object
|
||||
std::unique_lock<std::mutex> lock(m_mtxChannels);
|
||||
auto ptrCommunication =
|
||||
std::make_shared<CChannelConnector>(*this, static_cast<uint32_t>(m_vecChannels.size()), pChannelEndpoint);
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
|
||||
// exception was triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrCommunication)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to allocate SDV communication channel!");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Start the connection
|
||||
pProxy = ptrCommunication->ClientConnect(uiTimeoutMs);
|
||||
if (!pProxy)
|
||||
{
|
||||
SDV_LOG_ERROR("Could not connect client!");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Add the channel to the vector
|
||||
m_vecChannels.push_back(ptrCommunication);
|
||||
|
||||
return ptrCommunication->GetConnectionID();
|
||||
}
|
||||
|
||||
void CCommunicationControl::RemoveConnection(/*in*/ const sdv::com::TConnectionID& tConnectionID)
|
||||
{
|
||||
// Clear the vector entry.
|
||||
std::unique_lock<std::mutex> lock(m_mtxChannels);
|
||||
std::shared_ptr<CChannelConnector> ptrChannelsCopy;
|
||||
if (tConnectionID.uiIdent < m_vecChannels.size())
|
||||
{
|
||||
ptrChannelsCopy = m_vecChannels[tConnectionID.uiIdent]; // Keep the channel alive while clearing the vector entry.
|
||||
m_vecChannels[tConnectionID.uiIdent].reset(); // Only clear the pointer; do not remove the entry (this would mix up IDs).
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
// Clear the channel.
|
||||
ptrChannelsCopy.reset();
|
||||
}
|
||||
|
||||
sdv::interface_t CCommunicationControl::GetProxy(/*in*/ const sdv::ps::TMarshallID& tStubID, /*in*/ sdv::interface_id id)
|
||||
{
|
||||
if (!m_pConnectorContext) return {};
|
||||
|
||||
// Get the proxy
|
||||
std::shared_ptr<CMarshallObject> ptrProxy = m_pConnectorContext->GetOrCreateProxy(id, tStubID);
|
||||
if (!ptrProxy) return {};
|
||||
|
||||
return ptrProxy->GetProxy();
|
||||
}
|
||||
|
||||
sdv::ps::TMarshallID CCommunicationControl::GetStub(/*in*/ sdv::interface_t ifc)
|
||||
{
|
||||
// Get the stub
|
||||
auto ptrStub = GetOrCreateStub(ifc);
|
||||
if (!ptrStub) return {};
|
||||
|
||||
return ptrStub->GetMarshallID();
|
||||
}
|
||||
|
||||
std::shared_ptr<CMarshallObject> CCommunicationControl::CreateProxy(sdv::interface_id id, sdv::ps::TMarshallID tStubID,
|
||||
CChannelConnector& rConnector)
|
||||
{
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxObjects);
|
||||
|
||||
// Create the marshall object.
|
||||
size_t nIndex = m_vecMarshallObjects.size();
|
||||
auto ptrMarshallObject = std::make_shared<CMarshallObject>(*this);
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
|
||||
// exception was triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrMarshallObject)
|
||||
return {};
|
||||
m_vecMarshallObjects.push_back(ptrMarshallObject);
|
||||
if (!ptrMarshallObject->InitializeAsProxy(static_cast<uint32_t>(nIndex), id, tStubID, rConnector))
|
||||
ptrMarshallObject.reset();
|
||||
if (!ptrMarshallObject) return {};
|
||||
return ptrMarshallObject;
|
||||
}
|
||||
|
||||
std::shared_ptr<CMarshallObject> CCommunicationControl::GetOrCreateStub(sdv::interface_t ifc)
|
||||
{
|
||||
if (!ifc) return {};
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxObjects);
|
||||
|
||||
// TODO: Check the proxy list if the interface is a proxy object. If so, get the corresponding stub ID instead of creating
|
||||
// another stub object.
|
||||
|
||||
// If not existing, add an empty object in the map.
|
||||
auto itMarshallObject = m_mapStubObjects.find(ifc);
|
||||
if (itMarshallObject == m_mapStubObjects.end())
|
||||
{
|
||||
auto prMarshallObject = m_mapStubObjects.try_emplace(ifc, std::make_shared<CMarshallObject>(*this));
|
||||
if (!prMarshallObject.second) return {};
|
||||
itMarshallObject = prMarshallObject.first;
|
||||
size_t nIndex = m_vecMarshallObjects.size();
|
||||
m_vecMarshallObjects.push_back(itMarshallObject->second);
|
||||
if (!itMarshallObject->second->InitializeAsStub(static_cast<uint32_t>(nIndex), ifc))
|
||||
itMarshallObject->second.reset();
|
||||
}
|
||||
|
||||
return itMarshallObject->second;
|
||||
}
|
||||
|
||||
uint64_t CCommunicationControl::CreateUniqueCallIndex()
|
||||
{
|
||||
// Return the next call count.
|
||||
return m_uiCurrentCallCnt++;
|
||||
}
|
||||
|
||||
void CCommunicationControl::SetConnectorContext(CChannelConnector* pConnectorContext)
|
||||
{
|
||||
// Store the current channel for this thread (needed during the proxy creation).
|
||||
m_pConnectorContext = pConnectorContext;
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> CCommunicationControl::CallStub(sdv::ps::TMarshallID tStubID,
|
||||
sdv::sequence<sdv::pointer<uint8_t>>& seqInputData)
|
||||
{
|
||||
// Find stub and call the function
|
||||
std::unique_lock<std::recursive_mutex> lock(m_mtxObjects);
|
||||
if (tStubID.uiIdent >= m_vecMarshallObjects.size()) throw sdv::ps::XMarshallIntegrity();
|
||||
auto ptrMarshallObject = m_vecMarshallObjects[tStubID.uiIdent].lock();
|
||||
lock.unlock();
|
||||
|
||||
// Check for a valid object
|
||||
if (!ptrMarshallObject) throw sdv::ps::XMarshallIntegrity();
|
||||
if (ptrMarshallObject->GetMarshallID() != tStubID) throw sdv::ps::XMarshallIntegrity();
|
||||
|
||||
// Make the call
|
||||
return ptrMarshallObject->Call(seqInputData);
|
||||
}
|
||||
209
sdv_services/ipc_com/com_ctrl.h
Normal file
209
sdv_services/ipc_com/com_ctrl.h
Normal file
@@ -0,0 +1,209 @@
|
||||
#ifndef COM_CTRL_H
|
||||
#define COM_CTRL_H
|
||||
|
||||
#include <support/pssup.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <interfaces/com.h>
|
||||
|
||||
// Forward declaration
|
||||
class CChannelConnector;
|
||||
class CMarshallObject;
|
||||
|
||||
/**
|
||||
* @brief Test object simulating an component isolation service implementation for channel implementation testing
|
||||
*/
|
||||
class CCommunicationControl : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::com::IConnectionControl,
|
||||
public sdv::ps::IMarshallAccess
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor.
|
||||
*/
|
||||
CCommunicationControl();
|
||||
|
||||
/**
|
||||
* @brief Destructor added to cleanly stop service in case process is stopped (without shutdown via repository service)
|
||||
*/
|
||||
virtual ~CCommunicationControl() override;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ps::IMarshallAccess)
|
||||
SDV_INTERFACE_ENTRY(sdv::com::IConnectionControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Component declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
DECLARE_OBJECT_CLASS_NAME("CommunicationControl")
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
virtual void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
virtual sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
virtual void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Create an IPC channel endpoint and use it for SDV communication. Overload of
|
||||
* sdv::com::IConnectionControl::CreateServerConnection.
|
||||
* @remarks The channel will be destroyed automatically when a timeout occurs (no initial connection took place within the
|
||||
* specified time).
|
||||
* @remarks The function doesn't wait until a connection has been made, but returns straight after the channel creation.
|
||||
* @param[in] eChannelType Type of channel to create. Must be local or remote.
|
||||
* @param[in] pObject Initial object to start the communication with.
|
||||
* @param[in] uiTimeoutMs Timeout for waiting for a connection.
|
||||
* @param[out] ssConnectionString String describing the connection details to connect to this channel.
|
||||
* @return Channel connection ID if successful or 0 if not.
|
||||
*/
|
||||
virtual sdv::com::TConnectionID CreateServerConnection(/*in*/ sdv::com::EChannelType eChannelType,
|
||||
/*in*/ sdv::IInterfaceAccess* pObject, /*in*/ uint32_t uiTimeoutMs, /*out*/ sdv::u8string& ssConnectionString) override;
|
||||
|
||||
/**
|
||||
* @brief Connect to an SDV channel (an IPC channel managed by the channel control and waiting for an initial connection.
|
||||
* Overload of sdv::com::IConnectionControl::CreateClientConnection.
|
||||
* @remarks The connection will be destroyed automatically when a timeout occurs (no initial connection took place within
|
||||
* the specified time).
|
||||
* @param[in] ssConnectionString The string describing the connection details.
|
||||
* @param[in] uiTimeoutMs Timeout for waiting for a connection.
|
||||
* @param[out] pProxy Pointer to the object representing the remote object (a proxy to the remote object). Or nullptr
|
||||
* when a timeout occurred.
|
||||
* @return Channel connection ID if successful or 0 if not.
|
||||
*/
|
||||
virtual sdv::com::TConnectionID CreateClientConnection(/*in*/ const sdv::u8string& ssConnectionString,
|
||||
/*in*/ uint32_t uiTimeoutMs, /*out*/ sdv::IInterfaceAccess*& pProxy) override;
|
||||
|
||||
/**
|
||||
* @brief Assign and take over an already initialized IPC channel server endpoint for use with SDV communication. Overload of
|
||||
* sdv::com::IConnectionControl::AssignServerEndpoint.
|
||||
* @remarks The channel will be destroyed automatically when a timeout occurs (no initial connection took place within the
|
||||
* specified time) unless the flag bAllowReconnect has been set.
|
||||
* @remarks The channel uses the interface sdv::IObjectLifetime to control the lifetime of the channel. If IObjectLifetime is
|
||||
* not available, IObjectDestroy will be used.
|
||||
* @remarks The function doesn't wait until a connection has been made, but returns straight after the assignment.
|
||||
* @param[in] pChannelEndpoint Pointer to the channel endpoint to be assigned.
|
||||
* @param[in] pObject Initial object to start the communication with.
|
||||
* @param[in] uiTimeoutMs Timeout for waiting for an initial connection. Not used when the bAllowReconnect flag has been set.
|
||||
* @param[in] bAllowReconnect When set, the channel is allowed to be disconnected and reconnected again.
|
||||
* @return Channel connection ID if successful or 0 if not.
|
||||
*/
|
||||
virtual sdv::com::TConnectionID AssignServerEndpoint(/*in*/ sdv::IInterfaceAccess* pChannelEndpoint,
|
||||
/*in*/ sdv::IInterfaceAccess* pObject, /*in*/ uint32_t uiTimeoutMs, /*in*/ bool bAllowReconnect) override;
|
||||
|
||||
/**
|
||||
* @brief Assign and take over an already initialized IPC channel client endpoint for use with SDV communication. Overload of
|
||||
* sdv::com::IConnectionControl::AssignClientEndpoint.
|
||||
* @remarks The connection will be destroyed automatically when a timeout occurs (no initial connection took place
|
||||
* within the specified time).
|
||||
* @remarks The channel uses the interface sdv::IObjectLifetime to control the lifetime of the channel. If
|
||||
* IObjectLifetime is not available, IObjectDestroy will be used.
|
||||
* @param[in] pChannelEndpoint Pointer to the channel endpoint to be assigned.
|
||||
* @param[in] uiTimeoutMs Timeout for waiting for an initial connection. Not used when the bAllowReconnect flag has
|
||||
* been set.
|
||||
* @param[out] pProxy Pointer to the object representing the remote object (a proxy to the remote object). Or nullptr
|
||||
* when a timeout occurred.
|
||||
* @return Channel connection ID if successful or 0 if not.
|
||||
*/
|
||||
virtual sdv::com::TConnectionID AssignClientEndpoint(/*in*/ sdv::IInterfaceAccess* pChannelEndpoint,
|
||||
/*in*/ uint32_t uiTimeoutMs, /*out*/ sdv::IInterfaceAccess*& pProxy) override;
|
||||
|
||||
/**
|
||||
* @brief Remove a connection with the provided connection ID. Overload of sdv::com::IConnectionControl::RemoveConnection.
|
||||
* @param[in] tConnectionID The connection ID of the connection to remove.
|
||||
*/
|
||||
virtual void RemoveConnection(/*in*/ const sdv::com::TConnectionID& tConnectionID) override;
|
||||
|
||||
/**
|
||||
* @brief Get a proxy for the interface connection to the stub. Overload of sdv::ps::IMarshallAcess::GetProxy.
|
||||
* @param[in] tStubID Reference to the ID of the stub to connect to.
|
||||
* @param[in] id The interface ID to get the proxy for.
|
||||
* @return Returns the interface to the proxy object.
|
||||
*/
|
||||
virtual sdv::interface_t GetProxy(/*in*/ const sdv::ps::TMarshallID& tStubID, /*in*/ sdv::interface_id id) override;
|
||||
|
||||
/**
|
||||
* @brief Get a stub for the interface with the supplied ID. Overload of sdv::ps::IMarshallAcess::GetStub.
|
||||
* @param[in] ifc The interface to get the stub for..
|
||||
* @return Returns the Stub ID that is assigned to the interface. Or an empty ID when no stub could be found.
|
||||
*/
|
||||
virtual sdv::ps::TMarshallID GetStub(/*in*/ sdv::interface_t ifc) override;
|
||||
|
||||
/**
|
||||
* @brief Create a proxy for the interface connection to the stub.
|
||||
* @remarks Unlike stubs, which are unique for the process they run in, proxy objects are unique within the channel they are
|
||||
* used - using the identification of the stub ID of the process the call. The proxy object is not stored here; just created.
|
||||
* @param[in] id The ID of the interface this object marshalls the calls for.
|
||||
* @param[in] tStubID The stub ID this proxy is communicating to.
|
||||
* @param[in] rConnector Reference to channel connector.
|
||||
* @return Returns a shared pointer to the proxy object.
|
||||
*/
|
||||
std::shared_ptr<CMarshallObject> CreateProxy(sdv::interface_id id, sdv::ps::TMarshallID tStubID,
|
||||
CChannelConnector& rConnector);
|
||||
|
||||
/**
|
||||
* @brief Get a stub for the interface with the supplied ID. Overload of sdv::ps::IMarshallAcess::GetStub.
|
||||
* @param[in] ifc The interface to get the stub for..
|
||||
* @return Returns the Stub ID that is assigned to the interface. Or an empty ID when no stub could be found.
|
||||
*/
|
||||
std::shared_ptr<CMarshallObject> GetOrCreateStub(sdv::interface_t ifc);
|
||||
|
||||
/**
|
||||
* @brief To identify the send and receive packets belonging to one call, the call is identified with a unique index, which
|
||||
* is created here.
|
||||
* @return The unique call index.
|
||||
*/
|
||||
uint64_t CreateUniqueCallIndex();
|
||||
|
||||
/**
|
||||
* @brief Set the channel connector context for the current thread. This is used to marshall interfaces over the same connector.
|
||||
* @param[in] pConnectorContext Pointer to the connector currently being used.
|
||||
*/
|
||||
void SetConnectorContext(CChannelConnector* pConnectorContext);
|
||||
|
||||
/**
|
||||
* @brief Call the stub function.
|
||||
* @remarks This function call is synchronous and does not return until the call has been finalized or a timeout
|
||||
* exception has occurred.
|
||||
* @remarks The sequence contains all data to make the call. It is important that the data in the sequence is
|
||||
* complete and in the correct order.
|
||||
* @param[in] tStubID ID of the stub to call.
|
||||
* @param[inout] seqInputData Reference to sequence of input data pointers. The first data pointer contains the
|
||||
* marshalling header. The second contains the parameters (if available) and the others contain raw data pointers
|
||||
* (if available). The call is allowed to change the sequence to be able to add additional information during the
|
||||
* communication without having to copy the existing data.
|
||||
* @return Sequence of output data pointers. The first data pointer contains the marshalling header. The second
|
||||
* contains the return value and parameters (if available) and the others contain raw data pointers (if available).
|
||||
*/
|
||||
sdv::sequence<sdv::pointer<uint8_t>> CallStub(sdv::ps::TMarshallID tStubID, sdv::sequence<sdv::pointer<uint8_t>>& seqInputData);
|
||||
|
||||
private:
|
||||
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status.
|
||||
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::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
|
||||
std::atomic_uint64_t m_uiCurrentCallCnt = 0; ///< The current call count.
|
||||
thread_local static CChannelConnector* m_pConnectorContext; ///< The current connector; variable local to each thread.
|
||||
};
|
||||
DEFINE_SDV_OBJECT(CCommunicationControl)
|
||||
|
||||
#endif // !defined COM_CTRL_H
|
||||
176
sdv_services/ipc_com/marshall_object.cpp
Normal file
176
sdv_services/ipc_com/marshall_object.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include "marshall_object.h"
|
||||
#include "com_ctrl.h"
|
||||
#include <support/serdes.h>
|
||||
#include "com_channel.h"
|
||||
|
||||
CMarshallObject::CMarshallObject(CCommunicationControl& rcontrol) : m_rcontrol(rcontrol)
|
||||
{}
|
||||
|
||||
CMarshallObject::~CMarshallObject()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool CMarshallObject::IsValid() const
|
||||
{
|
||||
return m_tMarshallID.uiControl ? true : false;
|
||||
}
|
||||
|
||||
void CMarshallObject::Reset()
|
||||
{
|
||||
m_eType = EType::unknown;
|
||||
m_tMarshallID = { 0, 0, 0, 0 };
|
||||
m_pMarshall = nullptr;
|
||||
m_tStubID = {};
|
||||
m_ifcProxy = {};
|
||||
m_pConnector = nullptr;
|
||||
}
|
||||
|
||||
sdv::interface_t CMarshallObject::InitializeAsProxy(uint32_t uiProxyIndex, sdv::interface_id id,
|
||||
sdv::ps::TMarshallID tStubID, CChannelConnector& rConnector)
|
||||
{
|
||||
m_eType = EType::proxy;
|
||||
|
||||
// Create marshall ID from index and a random number.
|
||||
sdv::ps::TMarshallID tMarshallID = { 0, GetProcessID(), uiProxyIndex, static_cast<uint32_t>(rand()) };
|
||||
|
||||
// Get the stub creation interface from the repository
|
||||
sdv::core::IRepositoryMarshallCreate* pMarshallCreate =
|
||||
sdv::core::GetObject<sdv::core::IRepositoryMarshallCreate>("RepositoryService");
|
||||
if (!pMarshallCreate)
|
||||
{
|
||||
Reset();
|
||||
return {};
|
||||
}
|
||||
m_ptrMarshallObject = pMarshallCreate->CreateProxyObject(id);
|
||||
if (!m_ptrMarshallObject)
|
||||
{
|
||||
Reset();
|
||||
return {};
|
||||
}
|
||||
|
||||
// Set the proxy ID and control value
|
||||
sdv::ps::IMarshallObjectIdent* pObjectIdent = m_ptrMarshallObject.GetInterface<sdv::ps::IMarshallObjectIdent>();
|
||||
if (!pObjectIdent)
|
||||
{
|
||||
Reset();
|
||||
return {};
|
||||
}
|
||||
pObjectIdent->SetIdentification(tMarshallID);
|
||||
|
||||
// Set the connecting stub ID for this proxy and get the target interface that the proxy provides.
|
||||
sdv::ps::IProxyControl* pProxyControl = m_ptrMarshallObject.GetInterface<sdv::ps::IProxyControl>();
|
||||
if (!pProxyControl)
|
||||
{
|
||||
Reset();
|
||||
return {};
|
||||
}
|
||||
m_tStubID = tStubID;
|
||||
m_ifcProxy = pProxyControl->GetTargetInterface();
|
||||
if (!m_ifcProxy)
|
||||
{
|
||||
Reset();
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get the IMarshallLink on the proxy object interface to link to the proxy and connect to IDataSend...
|
||||
sdv::ps::IMarshallLink* pMarshallLink = m_ptrMarshallObject.GetInterface<sdv::ps::IMarshallLink>();
|
||||
if (!pMarshallLink)
|
||||
{
|
||||
Reset();
|
||||
return {};
|
||||
}
|
||||
pMarshallLink->Link(this);
|
||||
|
||||
// Store the channel.
|
||||
m_pConnector = &rConnector;
|
||||
|
||||
// Store the ID...
|
||||
m_tMarshallID = tMarshallID;
|
||||
|
||||
return m_ifcProxy;
|
||||
}
|
||||
|
||||
bool CMarshallObject::InitializeAsStub(uint32_t uiStubIndex, sdv::interface_t ifc)
|
||||
{
|
||||
if (!ifc) return false;
|
||||
|
||||
m_eType = EType::stub;
|
||||
|
||||
// Create marshall ID from index and a random number.
|
||||
sdv::ps::TMarshallID tMarshallID = { 0, GetProcessID(), uiStubIndex, static_cast<uint32_t>(rand()) };
|
||||
|
||||
// Get the stub creation interface from the repository
|
||||
sdv::core::IRepositoryMarshallCreate* pMarshallCreate =
|
||||
sdv::core::GetObject<sdv::core::IRepositoryMarshallCreate>("RepositoryService");
|
||||
if (!pMarshallCreate)
|
||||
{
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
m_ptrMarshallObject = pMarshallCreate->CreateStubObject(ifc.id());
|
||||
if (!m_ptrMarshallObject)
|
||||
{
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the stub ID and control value
|
||||
sdv::ps::IMarshallObjectIdent* pObjectIdent = m_ptrMarshallObject.GetInterface<sdv::ps::IMarshallObjectIdent>();
|
||||
if (!pObjectIdent)
|
||||
{
|
||||
Reset();
|
||||
return {};
|
||||
}
|
||||
pObjectIdent->SetIdentification(tMarshallID);
|
||||
|
||||
// Link the object to the stub.
|
||||
sdv::ps::IStubLink* psStubLink = m_ptrMarshallObject.GetInterface<sdv::ps::IStubLink>();
|
||||
if (!psStubLink)
|
||||
{
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
psStubLink->Link(ifc);
|
||||
|
||||
// Get the marshall interface
|
||||
m_pMarshall = m_ptrMarshallObject.GetInterface<sdv::ps::IMarshall>();
|
||||
if (!m_pMarshall)
|
||||
{
|
||||
Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Everything is successful. Store the ID...
|
||||
m_tMarshallID = tMarshallID;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
sdv::ps::TMarshallID CMarshallObject::GetMarshallID() const
|
||||
{
|
||||
return m_tMarshallID;
|
||||
}
|
||||
|
||||
sdv::interface_t CMarshallObject::GetProxy()
|
||||
{
|
||||
return m_ifcProxy;
|
||||
}
|
||||
|
||||
sdv::sequence<sdv::pointer<uint8_t>> CMarshallObject::Call(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqInputData)
|
||||
{
|
||||
// Differentiate between proxy and stub processing
|
||||
if (m_eType == EType::proxy)
|
||||
{
|
||||
// Make the call through the connector.
|
||||
if (!m_pConnector) throw sdv::ps::XMarshallNotInitialized();
|
||||
return m_pConnector->MakeCall(m_tMarshallID, m_tStubID, seqInputData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make the call through the stored marshall object.
|
||||
if (!m_pMarshall) throw sdv::ps::XMarshallNotInitialized();
|
||||
return m_pMarshall->Call(seqInputData);
|
||||
}
|
||||
}
|
||||
|
||||
117
sdv_services/ipc_com/marshall_object.h
Normal file
117
sdv_services/ipc_com/marshall_object.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#ifndef MARSHALL_OBJECT_H
|
||||
#define MARSHALL_OBJECT_H
|
||||
|
||||
#include <interfaces/ipc.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <support/interface_ptr.h>
|
||||
#include <interfaces/core_ps.h>
|
||||
|
||||
// Forward declarations
|
||||
class CCommunicationControl;
|
||||
class CChannelConnector;
|
||||
|
||||
inline sdv::process::TProcessID GetProcessID()
|
||||
{
|
||||
static sdv::process::TProcessID tProcessID = 0;
|
||||
if (!tProcessID)
|
||||
{
|
||||
const sdv::process::IProcessInfo* pProcessInfo = sdv::core::GetObject<sdv::process::IProcessInfo>("ProcessControlService");
|
||||
if (!pProcessInfo) return 0;
|
||||
tProcessID = pProcessInfo->GetProcessID();
|
||||
}
|
||||
return tProcessID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Storage class for a proxy or a stub object.
|
||||
*/
|
||||
class CMarshallObject : public sdv::ps::IMarshall
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rcontrol Reference to the communication control class.
|
||||
*/
|
||||
CMarshallObject(CCommunicationControl& rcontrol);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CMarshallObject();
|
||||
|
||||
/**
|
||||
* @brief Is this a valid marshal object?
|
||||
* @return Returns whether the marshal object is valid.
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* @brief Reset the marshal object.
|
||||
*/
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* @brief Initialize the marshall object as proxy.
|
||||
* @param[in] uiProxyIndex The index of this proxy; becoming part of the Proxy ID.
|
||||
* @param[in] id The ID of the interface this object marshalls the calls for.
|
||||
* @param[in] tStubID The stub ID this proxy is communicating to.
|
||||
* @param[in] rConnector Reference to channel connector.
|
||||
* @return Returns a pointer to proxy interface or empty when the initialization failed.
|
||||
*/
|
||||
sdv::interface_t InitializeAsProxy(uint32_t uiProxyIndex, sdv::interface_id id, sdv::ps::TMarshallID tStubID,
|
||||
CChannelConnector& rConnector);
|
||||
|
||||
/**
|
||||
* @brief Initialize the marshall object as stub.
|
||||
* @param[in] uiStubIndex The index of this stub; becoming part of the Stub ID.
|
||||
* @param[in] ifc Interface to the object to be marshalled to.
|
||||
* @return Returns 'true' when initialization was successful; 'false' when not.
|
||||
*/
|
||||
bool InitializeAsStub(uint32_t uiStubIndex, sdv::interface_t ifc);
|
||||
|
||||
/**
|
||||
* @brief Return the proxy/stub ID.
|
||||
* @details The marshall ID consist of an index to easily access the marshalling details and a control value to increase higher
|
||||
* security. The control value is a randomly generated value used in the communication to check whether the marshall object ID
|
||||
* is valid. Both index and control value must be known by the caller for the call to succeed. If one is wrong, the call won't
|
||||
* be made.
|
||||
* @return The ID of this marshall object.
|
||||
*/
|
||||
sdv::ps::TMarshallID GetMarshallID() const;
|
||||
|
||||
/**
|
||||
* @brief Return the proxy to the interface.
|
||||
* @return Proxy interface.
|
||||
*/
|
||||
sdv::interface_t GetProxy();
|
||||
|
||||
/**
|
||||
* @brief Marshall a function call. Ovverload of sdv::ps::IMarshall::Call.
|
||||
* @remarks This function call is synchronous and does not return until the call has been finalized or a timeout
|
||||
* exception has occurred.
|
||||
* @remarks The sequence contains all data to make the call. It is important that the data in the sequence is
|
||||
* complete and in the correct order.
|
||||
* @param[inout] seqInputData Reference to sequence of input data pointers. The first data pointer contains the
|
||||
* marshalling header. The second contains the parameters (if available) and the others contain raw data pointers
|
||||
* (if available). The call is allowed to change the sequence to be able to add additional information during the
|
||||
* communication without having to copy the existing data.
|
||||
* @return Sequence of output data pointers. The first data pointer contains the marshalling header. The second
|
||||
* contains the return value and parameters (if available) and the others contain raw data pointers (if available).
|
||||
*/
|
||||
virtual sdv::sequence<sdv::pointer<uint8_t>> Call(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqInputData) override;
|
||||
|
||||
private:
|
||||
/// Marshall object type
|
||||
enum class EType {unknown, proxy, stub};
|
||||
|
||||
CCommunicationControl& m_rcontrol; ///< Reference to the communication control class.
|
||||
EType m_eType = EType::unknown; ///< Type of object.
|
||||
sdv::ps::TMarshallID m_tMarshallID = {}; ///< The ID of this marshall object.
|
||||
sdv::ps::IMarshall* m_pMarshall = nullptr; ///< The marshall object pointer (only used for a stub).
|
||||
sdv::TObjectPtr m_ptrMarshallObject; ///< The marshall object.
|
||||
sdv::ps::TMarshallID m_tStubID = {}; ///< Stub ID (only used for a proxy).
|
||||
sdv::interface_t m_ifcProxy = {}; ///< Proxy interface (only used for a proxy).
|
||||
CChannelConnector* m_pConnector = nullptr; ///< Pointer to the connector (only used for a proxy).
|
||||
};
|
||||
|
||||
#endif // !defined MARSHALL_OBJECT_H
|
||||
39
sdv_services/ipc_connect/CMakeLists.txt
Normal file
39
sdv_services/ipc_connect/CMakeLists.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
# Include cross-compilation toolchain file
|
||||
include(../../cross-compile-tools.cmake)
|
||||
|
||||
# Define project
|
||||
project(ipc_connect VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(ipc_listener SHARED "listener.h" "listener.cpp")
|
||||
if (WIN32)
|
||||
target_link_libraries(ipc_listener ${CMAKE_THREAD_LIBS_INIT} Rpcrt4.lib)
|
||||
elseif(UNIX)
|
||||
target_link_libraries(ipc_listener rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
target_link_options(ipc_listener PRIVATE)
|
||||
set_target_properties(ipc_listener PROPERTIES PREFIX "")
|
||||
set_target_properties(ipc_listener PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(ipc_listener CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} ipc_listener PARENT_SCOPE)
|
||||
|
||||
# Define target
|
||||
add_library(ipc_connect SHARED "client.h" "client.cpp")
|
||||
if (WIN32)
|
||||
target_link_libraries(ipc_connect ${CMAKE_THREAD_LIBS_INIT} Rpcrt4.lib)
|
||||
elseif(UNIX)
|
||||
target_link_libraries(ipc_connect rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
target_link_options(ipc_connect PRIVATE)
|
||||
set_target_properties(ipc_connect PROPERTIES PREFIX "")
|
||||
set_target_properties(ipc_connect PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(ipc_connect CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} ipc_connect ipc_listener PARENT_SCOPE)
|
||||
216
sdv_services/ipc_connect/client.cpp
Normal file
216
sdv_services/ipc_connect/client.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#include "client.h"
|
||||
#include <support/toml.h>
|
||||
#include <interfaces/com.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include <support/pssup.h>
|
||||
#include <interfaces/app.h>
|
||||
|
||||
CRepositoryProxy::CRepositoryProxy(CClient& rClient, sdv::com::TConnectionID tConnection,
|
||||
sdv::IInterfaceAccess* pRepositoryProxy) :
|
||||
m_rClient(rClient), m_tConnection(tConnection), m_ptrRepositoryProxy(pRepositoryProxy)
|
||||
{}
|
||||
|
||||
void CRepositoryProxy::DestroyObject()
|
||||
{
|
||||
// Call the client to disconnect the connection and destroy the object.
|
||||
m_rClient.Disconnect(m_tConnection);
|
||||
}
|
||||
|
||||
void CClient::Initialize(const sdv::u8string& /*ssObjectConfig*/)
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CClient::GetStatus() const
|
||||
{
|
||||
return m_eObjectStatus;
|
||||
}
|
||||
|
||||
void CClient::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::running || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::configuring;
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::configuring || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::running;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CClient::Shutdown()
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CClient::Connect(const sdv::u8string& ssConnectString)
|
||||
{
|
||||
const sdv::app::IAppContext* pContext = sdv::core::GetCore<sdv::app::IAppContext>();
|
||||
if (!pContext)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to get application context!");
|
||||
return nullptr;
|
||||
}
|
||||
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
|
||||
if (!pConnectionControl)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to get communication control!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sdv::ipc::IChannelAccess* pChannelAccess = nullptr;
|
||||
std::string ssConfig;
|
||||
try
|
||||
{
|
||||
// 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!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException& rexcept)
|
||||
{
|
||||
SDV_LOG_ERROR("Invalid service configuration for listener service: ", rexcept.what(), "!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
sdv::TInterfaceAccessPtr ptrListenerProxy(pListenerProxy);
|
||||
|
||||
// Request for a private channel
|
||||
sdv::com::IRequestChannel* pRequestChannel = ptrListenerProxy.GetInterface<sdv::com::IRequestChannel>();
|
||||
if (!pRequestChannel)
|
||||
{
|
||||
SDV_LOG_ERROR("Could not get the channel creation interface!");
|
||||
if (tListenerConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tListenerConnection);
|
||||
return nullptr;
|
||||
}
|
||||
sdv::u8string ssConnectionString = pRequestChannel->RequestChannel("");
|
||||
|
||||
// Disconnect from the listener
|
||||
if (tListenerConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tListenerConnection);
|
||||
|
||||
if (ssConnectionString.empty())
|
||||
{
|
||||
SDV_LOG_ERROR("Could not get the private channel connection information!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: Use named mutex to prevent multiple connections at the same time.
|
||||
// Connect to the privatechannel.
|
||||
sdv::TObjectPtr ptrPrivateEndpoint = pChannelAccess->Access(ssConnectionString);
|
||||
|
||||
// Get and return the proxy
|
||||
sdv::IInterfaceAccess* pPrivateProxy = nullptr;
|
||||
sdv::com::TConnectionID tPrivateConnection = pConnectionControl->AssignClientEndpoint(ptrPrivateEndpoint, 5000, pPrivateProxy);
|
||||
ptrPrivateEndpoint.Clear(); // Lifetime has been taken over by communication control.
|
||||
if (!tPrivateConnection || !pPrivateProxy)
|
||||
{
|
||||
SDV_LOG_ERROR("Could not assign the client endpoint to the private channel!");
|
||||
if (tPrivateConnection != sdv::com::TConnectionID{}) pConnectionControl->RemoveConnection(tPrivateConnection);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create a remote repository object
|
||||
std::unique_lock<std::mutex> lock(m_mtxRepositoryProxies);
|
||||
m_mapRepositoryProxies.try_emplace(tPrivateConnection, *this, tPrivateConnection, pPrivateProxy);
|
||||
|
||||
return pPrivateProxy;
|
||||
}
|
||||
|
||||
void CClient::Disconnect(sdv::com::TConnectionID tConnectionID)
|
||||
{
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
|
||||
// Remove entry
|
||||
m_mapRepositoryProxies.erase(itRepository);
|
||||
}
|
||||
133
sdv_services/ipc_connect/client.h
Normal file
133
sdv_services/ipc_connect/client.h
Normal file
@@ -0,0 +1,133 @@
|
||||
#ifndef CLIENT_H
|
||||
#define CLIENT_H
|
||||
|
||||
#include <support/pssup.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <interfaces/com.h>
|
||||
|
||||
// Forward declaration.
|
||||
class CClient;
|
||||
|
||||
/**
|
||||
* @brief Class managing the connection and providing access to the server repository through a proxy.
|
||||
*/
|
||||
class CRepositoryProxy : public sdv::IInterfaceAccess, public sdv::IObjectDestroy
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rClient Reference to the client class.
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @brief Do not allow a copy constructor.
|
||||
* @param[in] rRepository Reference to the remote repository.
|
||||
*/
|
||||
CRepositoryProxy(const CRepositoryProxy& rRepository) = delete;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
SDV_INTERFACE_CHAIN_MEMBER(m_ptrRepositoryProxy)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Copy assignment is not allowed.
|
||||
* @param[in] rRepository Reference to the remote repository.
|
||||
*/
|
||||
CRepositoryProxy& operator=(const CRepositoryProxy& rRepository) = delete;
|
||||
|
||||
/**
|
||||
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
|
||||
* @attention After a call of this function, all exposed interfaces render invalid and should not be used any more.
|
||||
*/
|
||||
virtual void DestroyObject() override;
|
||||
|
||||
private:
|
||||
CClient& m_rClient; ///< Reference to the client object.
|
||||
sdv::com::TConnectionID m_tConnection = {}; ///< Connection ID.
|
||||
sdv::TInterfaceAccessPtr m_ptrRepositoryProxy; ///< Smart pointer to the remote repository.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Client object
|
||||
*/
|
||||
class CClient : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::com::IClientConnect
|
||||
{
|
||||
public:
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::com::IClientConnect)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declaration
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("ConnectionService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
void Shutdown() 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.
|
||||
*/
|
||||
virtual sdv::IInterfaceAccess* Connect(const sdv::u8string& ssConnectString) override;
|
||||
|
||||
/**
|
||||
* @brief Disconnect and remove the remote repository object.
|
||||
* @param[in] tConnectionID The ID of the connection.
|
||||
*/
|
||||
void Disconnect(sdv::com::TConnectionID tConnectionID);
|
||||
|
||||
private:
|
||||
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status.
|
||||
std::mutex m_mtxRepositoryProxies; ///< Protect access to the remnote repository map.
|
||||
std::map<sdv::com::TConnectionID, CRepositoryProxy> m_mapRepositoryProxies; ///< map of remote repositories.
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT(CClient)
|
||||
|
||||
|
||||
#endif // !defined CLIENT_H
|
||||
218
sdv_services/ipc_connect/listener.cpp
Normal file
218
sdv_services/ipc_connect/listener.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
#include "listener.h"
|
||||
#include <support/toml.h>
|
||||
#include <interfaces/com.h>
|
||||
#include <interfaces/app.h>
|
||||
#include <support/pssup.h>
|
||||
|
||||
CChannelBroker::CChannelBroker(CListener& rListener) : m_rListener(rListener)
|
||||
{}
|
||||
|
||||
sdv::u8string CChannelBroker::RequestChannel(/*in*/ const sdv::u8string& /*ssConfig*/)
|
||||
{
|
||||
// Get the communication control
|
||||
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
|
||||
if (!pConnectionControl)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to get communication control!");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get the repository
|
||||
sdv::TInterfaceAccessPtr ptrRespository = sdv::core::GetObject("RepositoryService");
|
||||
if (!ptrRespository)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to get repository service!");
|
||||
return {};
|
||||
}
|
||||
|
||||
// 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");
|
||||
if (!pEndpoint)
|
||||
{
|
||||
SDV_LOG_ERROR("No local channel control!");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Create the endpoint
|
||||
sdv::ipc::SChannelEndpoint sEndpoint = pEndpoint->CreateEndpoint(sdv::u8string());
|
||||
if (!sEndpoint.pConnection)
|
||||
{
|
||||
SDV_LOG_ERROR("Could not create the endpoint for channel request!");
|
||||
return sdv::u8string();
|
||||
}
|
||||
sdv::TObjectPtr ptrEndpoint(sEndpoint.pConnection); // Does automatic destruction if failure happens.
|
||||
|
||||
// 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.
|
||||
if (!tConnection)
|
||||
{
|
||||
SDV_LOG_ERROR("Could not assign the server endpoint!");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (sdv::app::ConsoleIsVerbose())
|
||||
std::cout << "Client connection established..." << std::endl;
|
||||
|
||||
// Return the connection string
|
||||
return sEndpoint.ssConnectString;
|
||||
}
|
||||
|
||||
CListener::CListener() : m_broker(*this)
|
||||
{}
|
||||
|
||||
void CListener::Initialize(const sdv::u8string& ssObjectConfig)
|
||||
{
|
||||
const sdv::app::IAppContext* pContext = sdv::core::GetCore<sdv::app::IAppContext>();
|
||||
if (!pContext)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to get application context!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
|
||||
if (!pConnectionControl)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to get communication control!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
sdv::ipc::ICreateEndpoint* pEndpoint = nullptr;
|
||||
std::string ssConfig;
|
||||
try
|
||||
{
|
||||
// Determine whether the service is running as server or as client.
|
||||
sdv::toml::CTOMLParser config(ssObjectConfig);
|
||||
std::string ssType = config.GetDirect("Listener.Type").GetValue();
|
||||
if (ssType == "Local")
|
||||
{
|
||||
uint32_t uiInstanceID = config.GetDirect("Listener.Instance").GetValue();
|
||||
m_bLocalListener = true;
|
||||
pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>("LocalChannelControl");
|
||||
if (!pEndpoint)
|
||||
{
|
||||
SDV_LOG_ERROR("No local channel control!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
// Request the instance ID from the app control
|
||||
ssConfig = std::string(R"code([IpcChannel]
|
||||
Name = "LISTENER_)code") + std::to_string(uiInstanceID ? uiInstanceID : pContext->GetInstanceID()) + R"code("
|
||||
Size = 2048
|
||||
)code";
|
||||
}
|
||||
else if (ssType == "Remote")
|
||||
{
|
||||
m_bLocalListener = false;
|
||||
std::string ssInterface = config.GetDirect("Listener.Interface").GetValue();
|
||||
uint32_t uiPort = config.GetDirect("Listener.Interface").GetValue();
|
||||
if (ssInterface.empty() || !uiPort)
|
||||
{
|
||||
SDV_LOG_ERROR("Missing interface or port number to initialize a remote listener!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>("RemoteChannelControl");
|
||||
if (!pEndpoint)
|
||||
{
|
||||
SDV_LOG_ERROR("No remote channel control!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
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!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (const sdv::toml::XTOMLParseException& rexcept)
|
||||
{
|
||||
SDV_LOG_ERROR("Invalid service configuration for listener service: ", rexcept.what(), "!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the endpoint
|
||||
sdv::ipc::SChannelEndpoint sEndpoint = pEndpoint->CreateEndpoint(ssConfig);
|
||||
if (!sEndpoint.pConnection)
|
||||
{
|
||||
SDV_LOG_ERROR("Could not create the endpoint for listener service!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
sdv::TObjectPtr ptrEndpoint(sEndpoint.pConnection); // Does automatic destruction if failure happens.
|
||||
|
||||
// Assign the endpoint to the communication service.
|
||||
m_tConnection = pConnectionControl->AssignServerEndpoint(ptrEndpoint, &m_broker, 100, true);
|
||||
ptrEndpoint.Clear(); // Lifetime taken over by communication control.
|
||||
if (!m_tConnection)
|
||||
{
|
||||
SDV_LOG_ERROR("Could not assign the server endpoint!");
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CListener::GetStatus() const
|
||||
{
|
||||
return m_eObjectStatus;
|
||||
}
|
||||
|
||||
void CListener::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::running || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::configuring;
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::configuring || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::running;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CListener::Shutdown()
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
|
||||
// Shutdown the listener...
|
||||
if (m_tConnection != sdv::com::TConnectionID{})
|
||||
{
|
||||
sdv::com::IConnectionControl* pConnectionControl = sdv::core::GetObject<sdv::com::IConnectionControl>("CommunicationControl");
|
||||
if (!pConnectionControl)
|
||||
SDV_LOG_ERROR("Failed to get communication control; cannot shutdown gracefully!");
|
||||
else
|
||||
pConnectionControl->RemoveConnection(m_tConnection);
|
||||
m_tConnection = {};
|
||||
}
|
||||
|
||||
m_ptrConnection.Clear();
|
||||
|
||||
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
|
||||
}
|
||||
|
||||
bool CListener::IsLocalListener() const
|
||||
{
|
||||
return m_bLocalListener;
|
||||
}
|
||||
|
||||
|
||||
113
sdv_services/ipc_connect/listener.h
Normal file
113
sdv_services/ipc_connect/listener.h
Normal file
@@ -0,0 +1,113 @@
|
||||
#ifndef LISTENER_H
|
||||
#define LISTENER_H
|
||||
|
||||
#include <support/component_impl.h>
|
||||
#include <interfaces/com.h>
|
||||
#include <interfaces/ipc.h>
|
||||
|
||||
// Forward declarations
|
||||
class CListener;
|
||||
|
||||
/**
|
||||
* @brief Channel broker to request new channels. This object is exposed to the client.
|
||||
*/
|
||||
class CChannelBroker : public sdv::IInterfaceAccess, public sdv::com::IRequestChannel
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rListener Reference to the listener to forward the calls to.
|
||||
*/
|
||||
CChannelBroker(CListener& rListener);
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::com::IRequestChannel)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Request a channel. Overload of sdv::com::IRequestChannel::RequestChannel
|
||||
* @details This function creates a new endpoint and returns access to the repository through the new channel.
|
||||
* @param[in] ssConfig Configuration; currently not used.
|
||||
* @return The channel string needed to initialize the channel.
|
||||
*/
|
||||
virtual sdv::u8string RequestChannel(/*in*/ const sdv::u8string& ssConfig) override;
|
||||
|
||||
private:
|
||||
CListener& m_rListener; ///< Reference to the listener to forward the calls to.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Listener object
|
||||
*/
|
||||
class CListener : public sdv::CSdvObject, public sdv::IObjectControl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CListener();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declaration
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("ConnectionListenerService")
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @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
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
void Shutdown() 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.
|
||||
*/
|
||||
bool IsLocalListener() const;
|
||||
|
||||
private:
|
||||
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< To update the object status when it changes.
|
||||
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.
|
||||
};
|
||||
|
||||
DEFINE_SDV_OBJECT(CListener)
|
||||
|
||||
#endif // ! defined LISTENER_H
|
||||
32
sdv_services/ipc_shared_mem/CMakeLists.txt
Normal file
32
sdv_services/ipc_shared_mem/CMakeLists.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
# Define project
|
||||
project(ipc_shared_mem VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(ipc_shared_mem SHARED
|
||||
"channel_mgnt.h"
|
||||
"channel_mgnt.cpp"
|
||||
"connection.h"
|
||||
"connection.cpp"
|
||||
"shared_mem_buffer_posix.h"
|
||||
"shared_mem_buffer_windows.h"
|
||||
"in_process_mem_buffer.h"
|
||||
"mem_buffer_accessor.h"
|
||||
"mem_buffer_accessor.cpp"
|
||||
"watchdog.h"
|
||||
"watchdog.cpp"
|
||||
)
|
||||
if(UNIX)
|
||||
target_link_libraries(ipc_shared_mem rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
target_link_libraries(ipc_shared_mem ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
target_link_options(ipc_shared_mem PRIVATE)
|
||||
target_include_directories(ipc_shared_mem PRIVATE ./include/)
|
||||
set_target_properties(ipc_shared_mem PROPERTIES PREFIX "")
|
||||
set_target_properties(ipc_shared_mem PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(ipc_shared_mem CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} ipc_shared_mem PARENT_SCOPE)
|
||||
97
sdv_services/ipc_shared_mem/channel_mgnt.cpp
Normal file
97
sdv_services/ipc_shared_mem/channel_mgnt.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "channel_mgnt.h"
|
||||
#include "connection.h"
|
||||
#include <support/toml.h>
|
||||
|
||||
void CSharedMemChannelMgnt::Initialize(const sdv::u8string& /*ssObjectConfig*/)
|
||||
{
|
||||
if (m_eObjectStatus != sdv::EObjectStatus::initialization_pending)
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
return;
|
||||
}
|
||||
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CSharedMemChannelMgnt::GetStatus() const
|
||||
{
|
||||
return m_eObjectStatus;
|
||||
}
|
||||
|
||||
void CSharedMemChannelMgnt::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::running || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::configuring;
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::configuring || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::running;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CSharedMemChannelMgnt::Shutdown()
|
||||
{
|
||||
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
m_watchdog.Clear();
|
||||
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
|
||||
}
|
||||
|
||||
sdv::ipc::SChannelEndpoint CSharedMemChannelMgnt::CreateEndpoint(/*in*/ const sdv::u8string& ssEndpointConfig)
|
||||
{
|
||||
std::string ssName;
|
||||
uint32_t uiSize = 10*1024;
|
||||
if (!ssEndpointConfig.empty())
|
||||
{
|
||||
sdv::toml::CTOMLParser config(ssEndpointConfig);
|
||||
ssName = static_cast<std::string>(config.GetDirect("IpcChannel.Name").GetValue());
|
||||
if (!ssName.empty())
|
||||
{
|
||||
uiSize = config.GetDirect("IpcChannel.Size").GetValue();
|
||||
if (!uiSize) uiSize = 128 * 1024;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a connection
|
||||
std::shared_ptr<CConnection> ptrConnection = std::make_shared<CConnection>(m_watchdog, uiSize, ssName, true);
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
|
||||
// exception was triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrConnection)
|
||||
return {};
|
||||
m_watchdog.AddConnection(ptrConnection);
|
||||
|
||||
// Return the connection details.
|
||||
sdv::ipc::SChannelEndpoint connectionEndpoint{};
|
||||
connectionEndpoint.pConnection = static_cast<IInterfaceAccess*>(ptrConnection.get());
|
||||
connectionEndpoint.ssConnectString = ptrConnection->GetConnectionString();
|
||||
return connectionEndpoint;
|
||||
}
|
||||
|
||||
sdv::IInterfaceAccess* CSharedMemChannelMgnt::Access(const sdv::u8string& ssConnectString)
|
||||
{
|
||||
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
|
||||
// to connect to the shared memory.
|
||||
std::shared_ptr<CConnection> ptrConnection;
|
||||
if (parser.GetDirect("Provider").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);
|
||||
}
|
||||
if (!ptrConnection) return {};
|
||||
m_watchdog.AddConnection(ptrConnection);
|
||||
|
||||
// Return the connection
|
||||
IInterfaceAccess* pInterface = ptrConnection.get();
|
||||
return pInterface;
|
||||
}
|
||||
120
sdv_services/ipc_shared_mem/channel_mgnt.h
Normal file
120
sdv_services/ipc_shared_mem/channel_mgnt.h
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef CHANNEL_MGNT_H
|
||||
#define CHANNEL_MGNT_H
|
||||
|
||||
#include <support/component_impl.h>
|
||||
#include <interfaces/ipc.h>
|
||||
#include "connection.h"
|
||||
#include "shared_mem_buffer_posix.h"
|
||||
#include "shared_mem_buffer_windows.h"
|
||||
#include "watchdog.h"
|
||||
#include "connection.h"
|
||||
|
||||
#define TEST_DECLARE_OBJECT_CLASS_ALIAS(...) \
|
||||
static sdv::sequence<sdv::u8string> GetClassAliasesStaticMyTest() \
|
||||
{ \
|
||||
return sdv::sequence<sdv::u8string>({__VA_ARGS__}); \
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief IPC channel management class for the shared memory communication.
|
||||
*/
|
||||
class CSharedMemChannelMgnt : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::ipc::ICreateEndpoint,
|
||||
public sdv::ipc::IChannelAccess
|
||||
{
|
||||
public:
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IChannelAccess)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::ICreateEndpoint)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("DefaultSharedMemoryChannelControl")
|
||||
DECLARE_OBJECT_CLASS_ALIAS("LocalChannelControl")
|
||||
DECLARE_DEFAULT_OBJECT_NAME("LocalChannelControl")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Create IPC connection object and return the endpoint information. Overload of
|
||||
* sdv::ipc::ICreateEndpoint::CreateEndpoint.
|
||||
* @details The endpoints are generated using either a size and a name based on the provided channel configuration or if no
|
||||
* configuration is supplied a default size of 10k and a randomly generated name. The following configuration
|
||||
* can be supplied:
|
||||
* @code
|
||||
* [IpcChannel]
|
||||
* Name = "CHANNEL_1234"
|
||||
* Size = 10240
|
||||
* @endcode
|
||||
* @param[in] ssChannelConfig Optional channel type specific endpoint configuration.
|
||||
* @return IPC connection object
|
||||
*/
|
||||
sdv::ipc::SChannelEndpoint CreateEndpoint(/*in*/ const sdv::u8string& ssChannelConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Create a connection object from the channel connection parameters string
|
||||
* @param[in] ssConnectString Reference to the string containing the channel connection parameters.
|
||||
* @return Pointer to IInterfaceAccess interface of the connection object or NULL when the object cannot be created.
|
||||
*/
|
||||
sdv::IInterfaceAccess* Access(const sdv::u8string& ssConnectString) override;
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* @brief Shared memory bridge
|
||||
* @attention The bridge is created here, but potentially used by two separated processed. To prevent channel destruction
|
||||
* keep the bridge alive.
|
||||
* @attention Under Posix, the unmapping in one process counts for all connections to this buffer within the process. Creating
|
||||
* additional buffer access interfaces might result in the buffer becoming invalid when one of them is removed again.
|
||||
*/
|
||||
struct SChannel
|
||||
{
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
SChannel() : bufferTargetTx(bufferOriginRx.GetConnectionString()), bufferTargetRx(bufferOriginTx.GetConnectionString())
|
||||
{
|
||||
if (!bufferOriginRx.IsValid()) std::cout << "Channel Origin RX is invalid!" << std::endl;
|
||||
if (!bufferOriginTx.IsValid()) std::cout << "Channel Origin TX is invalid!" << std::endl;
|
||||
if (!bufferTargetRx.IsValid()) std::cout << "Channel Target RX is invalid!" << std::endl;
|
||||
if (!bufferTargetTx.IsValid()) std::cout << "Channel Target TX is invalid!" << std::endl;
|
||||
}
|
||||
|
||||
CSharedMemBufferTx bufferOriginTx; ///< Origin Tx channel
|
||||
CSharedMemBufferRx bufferOriginRx; ///< Origin Rx channel
|
||||
CSharedMemBufferTx bufferTargetTx; ///< Target Tx channel
|
||||
CSharedMemBufferRx bufferTargetRx; ///< Target Rx channel
|
||||
};
|
||||
|
||||
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status.
|
||||
std::map<std::string, std::unique_ptr<SChannel>> m_mapChannels; ///< Map with channels.
|
||||
CWatchDog m_watchdog; ///< Process monitor for connections.
|
||||
};
|
||||
DEFINE_SDV_OBJECT(CSharedMemChannelMgnt)
|
||||
|
||||
#endif // ! defined CHANNEL_MGNT_H
|
||||
1126
sdv_services/ipc_shared_mem/connection.cpp
Normal file
1126
sdv_services/ipc_shared_mem/connection.cpp
Normal file
File diff suppressed because it is too large
Load Diff
453
sdv_services/ipc_shared_mem/connection.h
Normal file
453
sdv_services/ipc_shared_mem/connection.h
Normal file
@@ -0,0 +1,453 @@
|
||||
/**
|
||||
* @file connection.h
|
||||
* @author Erik Verhoeven FRD DISDS1 (mailto:erik.verhoeven@zf.com)
|
||||
* @brief Implementation of connection class.
|
||||
* @version 2.0
|
||||
* @date 2024-06-24
|
||||
*
|
||||
* @copyright Copyright ZF Friedrichshafen AG (c) 2023-2025
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CHANNEL_H
|
||||
#define CHANNEL_H
|
||||
|
||||
/// Enables the reporting of messages when set to...
|
||||
/// 1. info only (no protocol, no data)
|
||||
/// 2. info and protocol (no data)
|
||||
/// 3. info, protocol and data protocol
|
||||
/// 4. info, protocol, data protocol and data content
|
||||
#define ENABLE_REPORTING 0
|
||||
|
||||
/// When put to 1, decoupling of receive data is activated (default is not activated).
|
||||
#define ENABLE_DECOUPLING 0
|
||||
|
||||
#if ENABLE_REPORTING > 0
|
||||
/// Enable tracing
|
||||
#define ENABLE_TRACE 1
|
||||
#endif
|
||||
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
#include "in_process_mem_buffer.h"
|
||||
#include "shared_mem_buffer_posix.h"
|
||||
#include "shared_mem_buffer_windows.h"
|
||||
#include <interfaces/ipc.h>
|
||||
#include <interfaces/process.h>
|
||||
#include <support/interface_ptr.h>
|
||||
#include <support/local_service_access.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <queue>
|
||||
#include <list>
|
||||
#include "../../global/trace.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
#endif
|
||||
|
||||
// Forward declaration
|
||||
class CWatchDog;
|
||||
|
||||
/**
|
||||
* Class for local IPC connection
|
||||
* Created and managed by IPCAccess::AccessLocalIPCConnection(best use unique_ptr to store, so memory address stays
|
||||
* valid)
|
||||
*/
|
||||
class CConnection : public std::enable_shared_from_this<CConnection>, public sdv::IInterfaceAccess, public sdv::IObjectDestroy,
|
||||
public sdv::ipc::IDataSend, public sdv::ipc::IConnect
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief default constructor used by create endpoint - allocates new buffers m_Sender and m_Receiver
|
||||
* @param[in] rWatchDog Reference to the watch dog object monitoring the connected processes.
|
||||
* @param[in] uiSize Optional size of the buffer. If zero, a default buffer size of 10k is configured.
|
||||
* @param[in] rssName Optional name to be used for the connection. If empty, a random name is generated.
|
||||
* @param[in] bServer When set, the connection is the server connection; otherwise it is the client connection (determines the
|
||||
* initial communication).
|
||||
*/
|
||||
CConnection(CWatchDog& rWatchDog, uint32_t uiSize, const std::string& rssName, bool bServer);
|
||||
|
||||
/**
|
||||
* @brief Access existing connection
|
||||
* @param[in] rWatchDog Reference to the watch dog object monitoring the connected processes.
|
||||
* @param[in] rssConnectionString Reference to string with connection information.
|
||||
*/
|
||||
CConnection(CWatchDog& rWatchDog, const std::string& rssConnectionString);
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor needed for "delete this;".
|
||||
*/
|
||||
virtual ~CConnection();
|
||||
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IConnect)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief get the connection string for the sender and the receiver
|
||||
* @return Returns the connection string for the sender and the receiver together
|
||||
*/
|
||||
std::string GetConnectionString();
|
||||
|
||||
/**
|
||||
* @brief Sends data consisting of multiple data chunks via the IPC connection.
|
||||
* Overload of sdv::ipc::IDataSend::SendData.
|
||||
* @param[inout] seqData Sequence of data buffers to be sent. The sequence might be changed to optimize the communication
|
||||
* without having to copy the data.
|
||||
* @return Return 'true' if all data could be sent; 'false' otherwise.
|
||||
*/
|
||||
virtual bool SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override;
|
||||
|
||||
/**
|
||||
* @brief Establish a connection and start sending/receiving messages. Overload of
|
||||
* sdv::ipc::IConnect::AsyncConnect.
|
||||
* @param[in] pReceiver The message has to be forwarded.
|
||||
* @return Returns 'true' when a connection could be established. Use IConnectStatus or IConnectEventCallback to check the
|
||||
* connection state.
|
||||
*/
|
||||
virtual bool AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver) override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a connection to take place. Overload of sdv::ipc::IConnect::WaitForConnection.
|
||||
* @param[in] uiWaitMs Wait for a connection to take place. A value of 0 doesn't wait at all, a value of 0xffffffff
|
||||
* waits for infinite time.
|
||||
* @return Returns 'true' when a connection took place.
|
||||
*/
|
||||
virtual bool WaitForConnection(/*in*/ uint32_t uiWaitMs) override;
|
||||
|
||||
/**
|
||||
* @brief Cancel a wait for connection. Overload of sdv::ipc::IConnect::CancelWait.
|
||||
*/
|
||||
virtual void CancelWait() override;
|
||||
|
||||
// Suppress cppcheck warning. The destructor calls Disconnect without dynamic binding. This is correct so.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Disconnect from a connection. This will set the connect status to disconnected. Overload of
|
||||
* sdv::ipc::IConnect::Disconnect.
|
||||
*/
|
||||
virtual void Disconnect() override;
|
||||
|
||||
/**
|
||||
* @brief Register event callback interface. Overload of sdv::ipc::IConnect::RegisterStatusEventCallback.
|
||||
* @details Register a connection status event callback interface. The exposed interface must be of type
|
||||
* IConnectEventCallback. The registration will exist until a call to the unregister function with the returned cookie
|
||||
* or until the connection is terminated.
|
||||
* @param[in] pEventCallback Pointer to the object exposing the IConnectEventCallback interface.
|
||||
* @return The cookie assigned to the registration.
|
||||
*/
|
||||
virtual uint64_t RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
|
||||
|
||||
/**
|
||||
* @brief Unregister the status event callback with the returned cookie from the registration. Overload of
|
||||
* sdv::ipc::IConnect::UnregisterStatusEventCallback.
|
||||
* @param[in] uiCookie The cookie returned by a previous call to the registration function.
|
||||
*/
|
||||
virtual void UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie) override;
|
||||
|
||||
/**
|
||||
* @brief Get status of the connection. Overload of sdv::ipc::IConnect::GetStatus.
|
||||
* @return Returns the ipc::EConnectStatus struct
|
||||
*/
|
||||
virtual sdv::ipc::EConnectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Destroy the object. Overload of IObjectDestroy::DestroyObject.
|
||||
* @attention After a call of this function, all exposed interfaces render invalid and should not be used any more.
|
||||
*/
|
||||
virtual void DestroyObject() override;
|
||||
|
||||
/**
|
||||
* @brief Set the connection status and if needed call the event callback.
|
||||
* @param[in] eStatus The new status.
|
||||
*/
|
||||
void SetStatus(sdv::ipc::EConnectStatus eStatus);
|
||||
|
||||
/**
|
||||
* @brief Returns whether this is a server connection or a client connection.
|
||||
* @return The server connection flag. If 'true' the connection is a server connection; otherwise a client connection.
|
||||
*/
|
||||
bool IsServer() const;
|
||||
|
||||
#ifdef TIME_TRACKING
|
||||
/**
|
||||
* @brief Get the last fragment sent time. Used to detect gaps.
|
||||
* @return The last sent time.
|
||||
*/
|
||||
std::chrono::high_resolution_clock::time_point GetLastSentTime() const { return m_tpLastSent; }
|
||||
|
||||
/**
|
||||
* @brief Get the last fragment received time. Used to detect gaps.
|
||||
* @return The last received time.
|
||||
*/
|
||||
std::chrono::high_resolution_clock::time_point GetLastReceiveTime() const { return m_tpLastReceived; }
|
||||
|
||||
/**
|
||||
* @brief Get the last fragment received loop time. Used to detect gaps.
|
||||
* @return The last received loop time.
|
||||
*/
|
||||
std::chrono::duration<double> GetLargestReceiveLoopDuration() const { return m_durationLargestDeltaReceived; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if ENABLE_REPORTING > 0
|
||||
template <typename... TArgs>
|
||||
void Trace(TArgs... tArgs) const
|
||||
{
|
||||
return ::Trace("this=", static_cast<const void*>(this), " ", tArgs...);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Message type enum
|
||||
*/
|
||||
enum class EMsgType : uint32_t
|
||||
{
|
||||
sync_request = 0, ///< Sync request message (version check; no data).
|
||||
sync_answer = 1, ///< Sync answer message (version check; no data).
|
||||
connect_request = 10, ///< Connection initiation request (SConnectMsg is used)
|
||||
connect_answer = 11, ///< Connection answer request (SConnectMsg is used)
|
||||
connect_term = 90, ///< Connection terminated
|
||||
data = 0x10000000, ///< Data message
|
||||
data_fragment = 0x10000001, ///< Data fragment (if data is longer than 1/4th of the buffer).
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Message header
|
||||
*/
|
||||
struct SMsgHdr
|
||||
{
|
||||
uint32_t uiVersion; ///< Header version
|
||||
EMsgType eType; ///< Type of packet
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Connection initiation message
|
||||
*/
|
||||
struct SConnectMsg : SMsgHdr
|
||||
{
|
||||
sdv::process::TProcessID tProcessID; ///< Process ID needed for lifetime monitoring
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Fragmented data message header.
|
||||
*/
|
||||
struct SFragmentedMsgHdr : SMsgHdr
|
||||
{
|
||||
uint32_t uiTotalLength; ///< The total length the data has.
|
||||
uint32_t uiOffset; ///< Current offset of the data.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Event callback structure.
|
||||
*/
|
||||
struct SEventCallback
|
||||
{
|
||||
uint64_t uiCookie = 0; ///< Registration cookie
|
||||
sdv::ipc::IConnectEventCallback* pCallback = nullptr; ///< Pointer to the callback. Could be NULL when the callback
|
||||
///< was deleted.
|
||||
};
|
||||
|
||||
CWatchDog& m_rWatchDog; ///< Reference to the watch dog object monitoring
|
||||
///< the connected processes.
|
||||
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.
|
||||
std::atomic<sdv::ipc::EConnectStatus> m_eStatus = sdv::ipc::EConnectStatus::uninitialized; ///< the status of the connection
|
||||
sdv::ipc::IDataReceiveCallback* m_pReceiver = nullptr; ///< Receiver to pass the messages to if available
|
||||
std::shared_mutex m_mtxEventCallbacks; ///< Protect access to callback list. Only locking when
|
||||
///< inserting.
|
||||
std::list<SEventCallback> m_lstEventCallbacks; ///< List containing event callbacks. New callbacks will
|
||||
///< be inserted in front (called first). Removed
|
||||
///< callbacks are NULL; the entry stays to allow
|
||||
///< removal during a SetStatus call.
|
||||
mutable std::mutex m_mtxSend; ///< Synchronize all packages to be sent.
|
||||
std::mutex m_mtxConnect; ///< Connection mutex.
|
||||
std::condition_variable m_cvConnect; ///< Connection variable for connecting.
|
||||
std::condition_variable m_cvStartConnect; ///< Start connection variable for connecting.
|
||||
bool m_bStarted = false; ///< When set, the reception thread has started.
|
||||
bool m_bServer = false; ///< When set, the connection is a server connection.
|
||||
#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.
|
||||
std::condition_variable m_cvReceiveAvailable; ///< Condition variable synchronizing the processing.
|
||||
std::condition_variable m_cvReceiveProcessed; ///< Condition variable synchronizing the processing.
|
||||
#endif
|
||||
#ifdef TIME_TRACKING
|
||||
std::chrono::high_resolution_clock::time_point m_tpLastSent{}; ///< Last time a fragment was sent.
|
||||
std::chrono::high_resolution_clock::time_point m_tpLastReceived{}; ///< Last time a fragment was received.
|
||||
std::chrono::duration<double> m_durationLargestDeltaReceived; ///< Largest duration
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Raw send function.
|
||||
* @param[in] pData to be send
|
||||
* @param[in] uiDataLength size of the data to be sent
|
||||
* @return Returns number of bytes which has been sent
|
||||
*/
|
||||
uint32_t Send(const void* pData, uint32_t uiDataLength);
|
||||
|
||||
/**
|
||||
* @brief Templated send implementation
|
||||
* @tparam T Type of data (structure) to send
|
||||
* @param[in] rt Reference to the data (structure).
|
||||
* @return Returns 'true' on successful sending; otherwise returns 'false'.
|
||||
*/
|
||||
template <typename T>
|
||||
bool Send(const T& rt)
|
||||
{
|
||||
return Send(&rt, sizeof(rt)) == sizeof(rt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function to receive data, runs in a thread
|
||||
*/
|
||||
void ReceiveMessages();
|
||||
|
||||
/**
|
||||
* @brief Message context structure used when receiving data.
|
||||
*/
|
||||
class CMessage : public CAccessorRxPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor moving the packet content into the message.
|
||||
* @param[in] rPacket Reference to the packet to assign.
|
||||
*/
|
||||
CMessage(CAccessorRxPacket&& rPacket);
|
||||
|
||||
/**
|
||||
* @brief Destructor accepting the packet if not previously rejected by calling Reset.
|
||||
*/
|
||||
~CMessage();
|
||||
|
||||
/**
|
||||
* @brief Returns whether the message is valid (has at least the size of the required header).
|
||||
* @return
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* @brief Get the message header if the data is at least the size of the header.
|
||||
* @return The message header or an empty header.
|
||||
*/
|
||||
SMsgHdr GetMsgHdr() const;
|
||||
|
||||
/**
|
||||
* @brief Get the connect header if the data is at least the size of the header and has type connect header.
|
||||
* @return The connect header or an empty header.
|
||||
*/
|
||||
SConnectMsg GetConnectHdr() const;
|
||||
|
||||
/**
|
||||
* @brief Get the fragmented message header. if the data is at least the size of the header and has type fragmented header.
|
||||
* @return The fragmented message header or an empty header.
|
||||
*/
|
||||
SFragmentedMsgHdr GetFragmentedHdr() const;
|
||||
|
||||
//// The various headers have the SMsgHdr in common.
|
||||
//union
|
||||
//{
|
||||
// SMsgHdr sMsgHdr; ///< Current message header
|
||||
// SConnectMsg sConnectHdr; ///< Connect header
|
||||
// SFragmentedMsgHdr sFragmentHdr; ///< Fragment header
|
||||
// uint8_t rgData[std::max(sizeof(sConnectHdr), sizeof(sFragmentHdr))];
|
||||
//};
|
||||
//uint32_t uiSize = 0; ///< Complete size of the message (incl. size of the header)
|
||||
//uint32_t uiOffset = 0; ///< Current read offset within the message. Complete message when offset == size.
|
||||
|
||||
/**
|
||||
* @brief Trace the protocol data (dependent on ENABLE_REPORTING setting).
|
||||
* @param[in] rConnection Reference to the connection class containing the connection information.
|
||||
*/
|
||||
void PrintHeader(const CConnection& rConnection) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Data context structure
|
||||
*/
|
||||
struct SDataContext
|
||||
{
|
||||
uint32_t uiTotalSize = 0; ///< The total data size among all messages (without message header).
|
||||
uint32_t uiCurrentOffset = 0; ///< The current offset within the complete fragmented data to be filled during the read process.
|
||||
size_t nChunkIndex = 0; ///< The current chunk index that is to be filled during the read process.
|
||||
uint32_t uiChunkOffset = 0; ///< The offset within the current chunk of data to be filled during the read process.
|
||||
sdv::sequence<sdv::pointer<uint8_t>> seqDataChunks; ///< The data chunks allocated during table reading and available after uiCurrentOffset is identical to uiTotalSize.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Read the data size table (amount of data buffers followed by the size of each buffer).
|
||||
* @param[in] rMessage Reference to the message containing the table.
|
||||
* @param[in] rsDataCtxt Data context structure to be initialized - buffers will be allocated.
|
||||
* @return Returns the current offset of the data within the buffer following the table or 0 if the table could not be read.
|
||||
*/
|
||||
uint32_t ReadDataTable(CMessage& rMessage, SDataContext& rsDataCtxt);
|
||||
|
||||
/**
|
||||
* @brief Read the data chunk to the buffers created by the ReadDataTable function. Subsequent calls can be made to this
|
||||
* function to fill the buffers. The last call (when the chunk index passes the last index in the table) the data will be
|
||||
* dispatched.
|
||||
* @param[in] rMessage Reference to the message containing the table.
|
||||
* @param[in] uiOffset The offset within the message data to start reading the data chunk.
|
||||
* @param[in] rsDataCtxt Data context structure to be filled.
|
||||
* @return Returns 'true' if the table could be read successfully; false if not.
|
||||
*/
|
||||
bool ReadDataChunk(CMessage& rMessage, uint32_t uiOffset, SDataContext& rsDataCtxt);
|
||||
|
||||
#if ENABLE_DECOUPLING > 0
|
||||
/**
|
||||
* @brief Decoupled receive data. Prevents blocking the receive buffer while processing.
|
||||
*/
|
||||
void DecoupleReceive();
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Received a synchronization request.
|
||||
* @param[in] rMessage Reference to the message containing the request.
|
||||
*/
|
||||
void ReceiveSyncRequest(const CMessage& rMessage);
|
||||
|
||||
/**
|
||||
* @brief Received a connection request.
|
||||
* @param[in] rMessage Reference to the message containing the request.
|
||||
*/
|
||||
void ReceiveConnectRequest(const CMessage& rMessage);
|
||||
|
||||
/**
|
||||
* @brief Received a synchronization answer.
|
||||
* @param[in] rMessage Reference to the message containing the answer.
|
||||
*/
|
||||
void ReceiveSyncAnswer(const CMessage& rMessage);
|
||||
|
||||
/**
|
||||
* @brief Received a connection answer.
|
||||
* @param[in] rMessage Reference to the message containing the answer.
|
||||
*/
|
||||
void ReceiveConnectAnswer(const CMessage& rMessage);
|
||||
|
||||
/**
|
||||
* @brief Received a connection termination information.
|
||||
* @param[in] rMessage Reference to the message containing the information.
|
||||
*/
|
||||
void ReceiveConnectTerm(CMessage& rMessage);
|
||||
|
||||
/**
|
||||
* @brief Received data message.
|
||||
* @param[in] rMessage Reference to the message containing the information.
|
||||
* @param[in] rsDataCtxt Reference to the data message context.
|
||||
*/
|
||||
void ReceiveDataMessage(CMessage& rMessage, SDataContext& rsDataCtxt);
|
||||
|
||||
/**
|
||||
* @brief Received data fragment message.
|
||||
* @param[in] rMessage Reference to the message containing the information.
|
||||
* @param[in] rsDataCtxt Reference to the data message context.
|
||||
*/
|
||||
void ReceiveDataFragementMessage(CMessage& rMessage, SDataContext& rsDataCtxt);
|
||||
};
|
||||
|
||||
#endif // !define CHANNEL_H
|
||||
219
sdv_services/ipc_shared_mem/in_process_mem_buffer.h
Normal file
219
sdv_services/ipc_shared_mem/in_process_mem_buffer.h
Normal file
@@ -0,0 +1,219 @@
|
||||
#ifndef IN_PROCESS_MEM_BUFFER_H
|
||||
#define IN_PROCESS_MEM_BUFFER_H
|
||||
|
||||
#include <memory>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <condition_variable>
|
||||
#include <support/toml.h>
|
||||
#include "mem_buffer_accessor.h"
|
||||
|
||||
/**
|
||||
* @brief In-process memory buffer.
|
||||
*/
|
||||
template <class TAccessor>
|
||||
class CInProcMemBuffer : public TAccessor
|
||||
{
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
* @param[in] uiSize Size of the buffer (default is 1 MByte). Must not be zero.
|
||||
*/
|
||||
CInProcMemBuffer(uint32_t uiSize = 1048576);
|
||||
|
||||
/**
|
||||
* @brief Connection constructor
|
||||
* @param[in] rssConnectionString Reference to string with connection information.
|
||||
*/
|
||||
CInProcMemBuffer(const std::string& rssConnectionString);
|
||||
|
||||
/**
|
||||
* \brief Default destructor
|
||||
*/
|
||||
~CInProcMemBuffer() = default;
|
||||
|
||||
/**
|
||||
* @brief Return the connection string.
|
||||
* @return The connection string to connect to this buffer.
|
||||
*/
|
||||
std::string GetConnectionString() const;
|
||||
|
||||
/**
|
||||
* @brief Trigger listener that a write operation was completed.
|
||||
*/
|
||||
void TriggerDataSend() override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a write operation to be completed.
|
||||
* @param[in] uiTimeoutMs The amount of time (in ms) to wait for a trigger.
|
||||
* @return Returns 'true' when data was stored, 'false' when a timeout occurred.
|
||||
*/
|
||||
bool WaitForData(uint32_t uiTimeoutMs) const override;
|
||||
|
||||
/**
|
||||
* @brief Trigger listener that a read operation was completed.
|
||||
*/
|
||||
void TriggerDataReceive() override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a read operation to be completed.
|
||||
* @param[in] uiTimeoutMs The amount of time (in ms) to wait for a trigger.
|
||||
* @return Returns 'true' when data was stored, 'false' when a timeout occurred.
|
||||
*/
|
||||
bool WaitForFreeSpace(uint32_t uiTimeoutMs) const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Synchronization object
|
||||
*/
|
||||
struct SSync : public std::enable_shared_from_this<SSync>
|
||||
{
|
||||
std::mutex mtx; ///< Mutex to synchronize.
|
||||
std::condition_variable cv; ///< Condition variable to signal.
|
||||
};
|
||||
std::shared_ptr<SSync> m_ptrSyncTx; ///< Shared pointer to the TX synchronization object.
|
||||
std::shared_ptr<SSync> m_ptrSyncRx; ///< Shared pointer to the RX synchronization object.
|
||||
std::unique_ptr<uint8_t[]> m_ptrBuffer; ///< Smart pointer to the buffer (only for the allocator).
|
||||
uint32_t m_uiSize = 0; ///< The size of the buffer.
|
||||
std::string m_ssError; ///< The last reported error.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Inproc memory buffer used for reading.
|
||||
*/
|
||||
using CInProcMemBufferRx = CInProcMemBuffer<CMemBufferAccessorRx>;
|
||||
|
||||
/**
|
||||
* @brief Inproc memory buffer used for writing.
|
||||
*/
|
||||
using CInProcMemBufferTx = CInProcMemBuffer<CMemBufferAccessorTx>;
|
||||
|
||||
template <class TAccessor>
|
||||
inline CInProcMemBuffer<TAccessor>::CInProcMemBuffer(uint32_t uiSize) :
|
||||
m_ptrSyncTx(new SSync), m_ptrSyncRx(new SSync), m_ptrBuffer(std::unique_ptr<uint8_t[]>(new uint8_t[uiSize])), m_uiSize(uiSize)
|
||||
{
|
||||
assert(uiSize);
|
||||
if (!uiSize) return;
|
||||
TAccessor::Attach(m_ptrBuffer.get(), uiSize);
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline CInProcMemBuffer<TAccessor>::CInProcMemBuffer(const std::string& rssConnectionString)
|
||||
{
|
||||
if (rssConnectionString.empty())
|
||||
{
|
||||
m_ssError = "Missing connection string.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Interpret the connection string
|
||||
sdv::toml::CTOMLParser config(rssConnectionString);
|
||||
|
||||
// The connection string can contain multiple parameters. Search for the first parameters fitting the accessor direction
|
||||
size_t nIndex = 0;
|
||||
sdv::toml::CNodeCollection nodeConnectParamCollection = config.GetDirect("ConnectParam");
|
||||
uint64_t uiLocation = 0ull, uiSyncTx = 0ull, uiSyncRx = 0ull;
|
||||
do
|
||||
{
|
||||
sdv::toml::CNodeCollection nodeConnectParam;
|
||||
switch (nodeConnectParamCollection.GetType())
|
||||
{
|
||||
case sdv::toml::ENodeType::node_array:
|
||||
if (nIndex >= nodeConnectParamCollection.GetCount()) break;
|
||||
nodeConnectParam = nodeConnectParamCollection[nIndex];
|
||||
break;
|
||||
case sdv::toml::ENodeType::node_table:
|
||||
if (nIndex > 0) break;
|
||||
nodeConnectParam = nodeConnectParamCollection;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (nodeConnectParam.GetType() != sdv::toml::ENodeType::node_table) break;
|
||||
|
||||
nIndex++;
|
||||
|
||||
// Check for shared memory
|
||||
if (nodeConnectParam.GetDirect("Type").GetValue() != "inproc_mem") continue;
|
||||
|
||||
// Check the direction
|
||||
if (nodeConnectParam.GetDirect("Direction").GetValue() !=
|
||||
(TAccessor::GetAccessType() == EAccessType::rx ? "response" : "request"))
|
||||
continue;
|
||||
|
||||
// Get the information
|
||||
uiLocation = nodeConnectParam.GetDirect("Location").GetValue();
|
||||
m_uiSize = static_cast<uint32_t>(nodeConnectParam.GetDirect("Size").GetValue());
|
||||
uiSyncTx = nodeConnectParam.GetDirect("SyncTx").GetValue();
|
||||
uiSyncRx = nodeConnectParam.GetDirect("SyncRx").GetValue();
|
||||
break;
|
||||
} while (true);
|
||||
if (!uiLocation || !uiSyncTx || !uiSyncRx)
|
||||
{
|
||||
m_ssError = "Incomplete connection information.";
|
||||
return;
|
||||
}
|
||||
|
||||
m_ptrSyncTx = reinterpret_cast<SSync*>(uiSyncTx)->shared_from_this();
|
||||
m_ptrSyncRx = reinterpret_cast<SSync*>(uiSyncRx)->shared_from_this();
|
||||
TAccessor::Attach(reinterpret_cast<uint8_t*>(uiLocation));
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline std::string CInProcMemBuffer<TAccessor>::GetConnectionString() const
|
||||
{
|
||||
// The connection string contains the TOML file for connecting to this shared memory.
|
||||
std::stringstream sstream;
|
||||
sstream << "[[ConnectParam]]" << std::endl;
|
||||
sstream << "Type = \"inproc_mem\"" << std::endl;
|
||||
sstream << "Location = " << reinterpret_cast<uint64_t>(TAccessor::GetBufferPointer()) << std::endl;
|
||||
sstream << "Size = " << m_uiSize << std::endl;
|
||||
sstream << "SyncTx = " << reinterpret_cast<uint64_t>(m_ptrSyncTx.get()) << std::endl;
|
||||
sstream << "SyncRx = " << reinterpret_cast<uint64_t>(m_ptrSyncRx.get()) << std::endl;
|
||||
// The target direction is the opposite of the direction of the accessor. Therefore, if the accessor uses an RX access type,
|
||||
// the target uses an TX access type and should be configured as response, otherwise it is a request.
|
||||
sstream << "Direction = \"" << (TAccessor::GetAccessType() == EAccessType::rx ? "request" : "response") << "\"" << std::endl;
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline void CInProcMemBuffer<TAccessor>::TriggerDataSend()
|
||||
{
|
||||
if (!m_ptrSyncTx) return;
|
||||
std::unique_lock lock(m_ptrSyncTx->mtx);
|
||||
m_ptrSyncTx->cv.notify_all();
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline bool CInProcMemBuffer<TAccessor>::WaitForData(uint32_t uiTimeoutMs) const
|
||||
{
|
||||
if (!m_ptrSyncTx) return false;
|
||||
|
||||
// Check whether there is data; if so, return true.
|
||||
if (TAccessor::HasUnreadData()) return true;
|
||||
|
||||
std::unique_lock lock(m_ptrSyncTx->mtx);
|
||||
return m_ptrSyncTx->cv.wait_for(lock, std::chrono::milliseconds(uiTimeoutMs)) == std::cv_status::no_timeout;
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline void CInProcMemBuffer<TAccessor>::TriggerDataReceive()
|
||||
{
|
||||
if (!m_ptrSyncRx) return;
|
||||
std::unique_lock lock(m_ptrSyncRx->mtx);
|
||||
m_ptrSyncRx->cv.notify_all();
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline bool CInProcMemBuffer<TAccessor>::WaitForFreeSpace(uint32_t uiTimeoutMs) const
|
||||
{
|
||||
if (!m_ptrSyncRx) return false;
|
||||
|
||||
std::unique_lock lock(m_ptrSyncRx->mtx);
|
||||
if (TAccessor::Canceled())
|
||||
return false;
|
||||
return m_ptrSyncRx->cv.wait_for(lock, std::chrono::milliseconds(uiTimeoutMs)) == std::cv_status::no_timeout;
|
||||
}
|
||||
|
||||
#endif // !defined(IN_PROCESS_MEM_BUFFER_H)
|
||||
529
sdv_services/ipc_shared_mem/mem_buffer_accessor.cpp
Normal file
529
sdv_services/ipc_shared_mem/mem_buffer_accessor.cpp
Normal file
@@ -0,0 +1,529 @@
|
||||
#include "mem_buffer_accessor.h"
|
||||
#include <cassert>
|
||||
|
||||
void CMemBufferAccessorBase::Attach(uint8_t* pBuffer, uint32_t uiSize /*= 0*/)
|
||||
{
|
||||
// Attach is only allowed to be called once
|
||||
assert(!m_pHdr && !m_pBuffer);
|
||||
if (m_pHdr || m_pBuffer)
|
||||
{
|
||||
std::stringstream sstream;
|
||||
sstream << "Accessor: Attaching is only allowed once (";
|
||||
if (m_pHdr) sstream << "header";
|
||||
if (m_pHdr && m_pBuffer) sstream << ", ";
|
||||
if (m_pBuffer) sstream << "buffer";
|
||||
sstream << ")" << std::endl;
|
||||
std::cout << sstream.str();
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!uiSize || uiSize > sizeof(SBufferHdr));
|
||||
|
||||
// Assign the buffer
|
||||
assert(pBuffer);
|
||||
m_pHdr = reinterpret_cast<SBufferHdr*>(pBuffer);
|
||||
if (!m_pHdr)
|
||||
{
|
||||
std::cout << "Accessor: header is NULL" << std::endl;
|
||||
return;
|
||||
}
|
||||
m_pBuffer = pBuffer + static_cast<uint32_t>(sizeof(SBufferHdr));
|
||||
|
||||
// If a size has been provided, initialize the header
|
||||
if (uiSize)
|
||||
{
|
||||
*m_pHdr = SBufferHdr();
|
||||
m_pHdr->uiSize = uiSize - static_cast<uint32_t>(sizeof(SBufferHdr));
|
||||
}
|
||||
|
||||
// Check for correct interface version (to prevent misaligned communication).
|
||||
assert(m_pHdr->uiVersion == SDVFrameworkInterfaceVersion);
|
||||
|
||||
// Check for the size to be larger than the buffer header and 64-bit aligned
|
||||
assert(m_pHdr->uiSize > sizeof(SBufferHdr));
|
||||
assert(m_pHdr->uiSize % 8 == 0);
|
||||
if (m_pHdr->uiVersion != SDVFrameworkInterfaceVersion || m_pHdr->uiSize <= sizeof(SBufferHdr) || m_pHdr->uiSize % 8 != 0)
|
||||
{
|
||||
m_pHdr = nullptr;
|
||||
m_pBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CMemBufferAccessorBase::Detach()
|
||||
{
|
||||
m_pBuffer = nullptr;
|
||||
m_pHdr = nullptr;
|
||||
|
||||
}
|
||||
|
||||
bool CMemBufferAccessorBase::IsValid() const
|
||||
{
|
||||
return m_pHdr && m_pBuffer;
|
||||
}
|
||||
|
||||
const uint8_t* CMemBufferAccessorBase::GetBufferPointer() const
|
||||
{
|
||||
return reinterpret_cast<uint8_t*>(m_pHdr);
|
||||
}
|
||||
|
||||
CAccessorTxPacket::CAccessorTxPacket(CMemBufferAccessorTx& rAccessor, CMemBufferAccessorBase::SPacketHdr* pPacketHdr) :
|
||||
m_pAccessor(&rAccessor)
|
||||
{
|
||||
// Checks
|
||||
if (!pPacketHdr) return;
|
||||
if (pPacketHdr->eType != CMemBufferAccessorBase::SPacketHdr::EType::data) return;
|
||||
if (pPacketHdr->eState != CMemBufferAccessorBase::SPacketHdr::EState::reserved) return;
|
||||
m_pPacketHdr = pPacketHdr;
|
||||
}
|
||||
|
||||
CAccessorTxPacket::CAccessorTxPacket(CAccessorTxPacket&& rpacket) noexcept:
|
||||
m_pAccessor(rpacket.m_pAccessor), m_pPacketHdr(rpacket.m_pPacketHdr)
|
||||
{
|
||||
rpacket.m_pAccessor = nullptr;
|
||||
rpacket.m_pPacketHdr = nullptr;
|
||||
}
|
||||
|
||||
CAccessorTxPacket::~CAccessorTxPacket()
|
||||
{
|
||||
Commit();
|
||||
}
|
||||
|
||||
CAccessorTxPacket& CAccessorTxPacket::operator=(CAccessorTxPacket&& rpacket) noexcept
|
||||
{
|
||||
m_pAccessor = rpacket.m_pAccessor;
|
||||
m_pPacketHdr = rpacket.m_pPacketHdr;
|
||||
rpacket.m_pAccessor = nullptr;
|
||||
rpacket.m_pPacketHdr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAccessorTxPacket::operator bool() const
|
||||
{
|
||||
return IsValid();
|
||||
}
|
||||
|
||||
bool CAccessorTxPacket::IsValid() const
|
||||
{
|
||||
return m_pPacketHdr;
|
||||
}
|
||||
|
||||
void CAccessorTxPacket::Commit()
|
||||
{
|
||||
if (m_pAccessor && m_pPacketHdr)
|
||||
m_pAccessor->Commit(m_pPacketHdr);
|
||||
m_pPacketHdr = nullptr;
|
||||
}
|
||||
|
||||
uint32_t CAccessorTxPacket::GetSize() const
|
||||
{
|
||||
return m_pPacketHdr ? m_pPacketHdr->uiSize : 0;
|
||||
}
|
||||
|
||||
uint8_t* CAccessorTxPacket::GetDataPtr()
|
||||
{
|
||||
return GetSize() ? reinterpret_cast<uint8_t*>(m_pPacketHdr + 1) : 0;
|
||||
}
|
||||
|
||||
CMemBufferAccessorTx::~CMemBufferAccessorTx()
|
||||
{
|
||||
m_bBlockReserve = true;
|
||||
|
||||
// Wait until all reserved packets are committed (could cause a crash otherwise).
|
||||
std::unique_lock<std::mutex> lock(m_mtxReservedPackes);
|
||||
|
||||
// Remove any committed packets
|
||||
while (!m_queueReservedPackets.empty())
|
||||
{
|
||||
uint32_t uiTxPos = m_pHdr->uiTxPos;
|
||||
SPacketHdr* pPacketHdr = m_queueReservedPackets.front();
|
||||
|
||||
// Is the top most packet not in committed state, we'll wait.
|
||||
if (pPacketHdr->eState != SPacketHdr::EState::commit)
|
||||
{
|
||||
lock.unlock();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
lock.lock();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the write position
|
||||
uiTxPos =
|
||||
Align(static_cast<uint32_t>(reinterpret_cast<uint8_t*>(pPacketHdr) - m_pBuffer + sizeof(SPacketHdr))
|
||||
+ pPacketHdr->uiSize);
|
||||
|
||||
// In case the write position is pointing to the end of the buffer, jump to the begin.
|
||||
if (uiTxPos >= m_pHdr->uiSize)
|
||||
{
|
||||
uiTxPos = 0;
|
||||
}
|
||||
|
||||
// Remove from queue
|
||||
m_queueReservedPackets.pop_front();
|
||||
m_pHdr->uiTxPos = uiTxPos;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<CAccessorTxPacket> CMemBufferAccessorTx::Reserve(uint32_t uiSize, uint32_t uiTimeoutMs)
|
||||
{
|
||||
if (m_bBlockReserve) return {};
|
||||
if (!IsValid()) return {};
|
||||
if (uiSize > m_pHdr->uiSize) return {};
|
||||
|
||||
uint32_t uiTxPos = 0;
|
||||
bool bStuffingNeeded = false;
|
||||
while (!m_bCancel)
|
||||
{
|
||||
// Create a snapshot of the read and write positions
|
||||
// If exists, use the last stored position in the queue. Otherwise use the position from the header.
|
||||
uint32_t uiRxPos = m_pHdr->uiRxPos;
|
||||
uiTxPos = m_pHdr->uiTxPos;
|
||||
std::unique_lock<std::mutex> lock(m_mtxReservedPackes);
|
||||
if (!m_queueReservedPackets.empty())
|
||||
uiTxPos = Align(reinterpret_cast<uint8_t*>(m_queueReservedPackets.back()) - m_pBuffer + sizeof(SPacketHdr)
|
||||
+ m_queueReservedPackets.back()->uiSize);
|
||||
|
||||
// Calculate the needed size (incl header) aligned to 64 bits.
|
||||
uint32_t uiNeededSize = Align(uiSize + static_cast<uint32_t>(sizeof(SPacketHdr)));
|
||||
|
||||
// If the read position is beyond the write position, the available space is the diference
|
||||
// If the read position is behind the write position, the available space is eiher until the end of the buffer or if not
|
||||
// fitting from the beginning of the buffer until the read position.
|
||||
uint32_t uiMaxSize = 0;
|
||||
bStuffingNeeded = false;
|
||||
if (uiRxPos > uiTxPos) // uiTxPos made a roundtrip already
|
||||
uiMaxSize = uiRxPos - uiTxPos - 1; // The last possible writing position is one before the reading position
|
||||
else // uiRxPos is running after uiTxPos
|
||||
{
|
||||
// uiMaxSize is the rest of the buffer
|
||||
uiMaxSize = m_pHdr->uiSize > uiTxPos ? m_pHdr->uiSize - uiTxPos : 0;
|
||||
|
||||
// When uiRxPos is at the beginning, this is a special situation, max size is the rest minus 1
|
||||
if (!uiRxPos)
|
||||
uiMaxSize--;
|
||||
else if (uiMaxSize < uiNeededSize)
|
||||
{
|
||||
bStuffingNeeded = true;
|
||||
uiMaxSize = uiRxPos - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for size
|
||||
if (uiNeededSize <= uiMaxSize)
|
||||
break;
|
||||
|
||||
// Wait for a reserve
|
||||
if (!WaitForFreeSpace(uiTimeoutMs))
|
||||
return {};
|
||||
}
|
||||
|
||||
if (m_bCancel) return {};
|
||||
|
||||
// Stuffing needed?
|
||||
if (bStuffingNeeded)
|
||||
{
|
||||
// Create stuffing packet... but only if the header still fits
|
||||
if (m_pHdr->uiSize - uiTxPos >= static_cast<uint32_t>(sizeof(SPacketHdr)))
|
||||
{
|
||||
SPacketHdr* pStuffPacket = GetPacketHdr(uiTxPos);
|
||||
pStuffPacket->eType = SPacketHdr::EType::stuffing;
|
||||
pStuffPacket->uiSize = m_pHdr->uiSize - uiTxPos - static_cast<uint32_t>(sizeof(SPacketHdr));
|
||||
pStuffPacket->eState = SPacketHdr::EState::commit;
|
||||
}
|
||||
else if (uiTxPos < m_pHdr->uiSize)
|
||||
std::fill_n(m_pBuffer + uiTxPos, m_pHdr->uiSize - uiTxPos, static_cast<uint8_t>(0));
|
||||
|
||||
// After stuffing, the new location is at the beginning of the buffer.
|
||||
uiTxPos = 0;
|
||||
}
|
||||
|
||||
// Prepare a packet
|
||||
SPacketHdr* pPacket = GetPacketHdr(uiTxPos);
|
||||
pPacket->eType = SPacketHdr::EType::data;
|
||||
pPacket->uiSize = uiSize;
|
||||
pPacket->eState = SPacketHdr::EState::reserved;
|
||||
|
||||
// Add the packet to the queue
|
||||
m_queueReservedPackets.push_back(pPacket);
|
||||
|
||||
// Create the packet
|
||||
return CAccessorTxPacket(*this, pPacket);
|
||||
}
|
||||
|
||||
void CMemBufferAccessorTx::Commit(SPacketHdr* pPacketHdr)
|
||||
{
|
||||
if (!IsValid()) return;
|
||||
if (!pPacketHdr) return;
|
||||
|
||||
// pData needs to point to an area in the buffer starting at the offset of a packet header.
|
||||
if (reinterpret_cast<uint8_t*>(pPacketHdr) > m_pBuffer + m_pHdr->uiSize)
|
||||
return; // Pointing beyond the buffer
|
||||
if (reinterpret_cast<uint8_t*>(pPacketHdr) < m_pBuffer)
|
||||
return; // Pointing before the first possible packet header
|
||||
|
||||
// Check packet header
|
||||
if (pPacketHdr->eType != SPacketHdr::EType::data)
|
||||
return; // Must be of type data
|
||||
if (pPacketHdr->eState != SPacketHdr::EState::reserved)
|
||||
return; // Must have reserved state
|
||||
if (reinterpret_cast<uint8_t*>(pPacketHdr) + pPacketHdr->uiSize + sizeof(SPacketHdr) > m_pBuffer + m_pHdr->uiSize)
|
||||
return; // Size cannot be beyond buffer
|
||||
|
||||
// Commit the packet
|
||||
pPacketHdr->eState = SPacketHdr::EState::commit;
|
||||
|
||||
// Trigger processing
|
||||
TriggerDataSend();
|
||||
|
||||
// Run through the queue and check whether the top most packet is actually committed
|
||||
std::unique_lock<std::mutex> lock(m_mtxReservedPackes);
|
||||
uint32_t uiTxPos = m_pHdr->uiTxPos;
|
||||
while (!m_queueReservedPackets.empty())
|
||||
{
|
||||
SPacketHdr* pPacketHdr2 = m_queueReservedPackets.front();
|
||||
|
||||
// Is the top most packet not in committed state, we're done.
|
||||
if (pPacketHdr2->eState != SPacketHdr::EState::commit) break;
|
||||
|
||||
// Update the write position
|
||||
uiTxPos = Align(static_cast<uint32_t>(reinterpret_cast<uint8_t*>(pPacketHdr2) - m_pBuffer + sizeof(SPacketHdr))
|
||||
+ pPacketHdr2->uiSize);
|
||||
|
||||
// In case the write position is pointing to the end of the buffer, jump to the begin.
|
||||
if (uiTxPos >= m_pHdr->uiSize)
|
||||
{
|
||||
uiTxPos = 0;
|
||||
}
|
||||
|
||||
// Remove from queue
|
||||
m_queueReservedPackets.pop_front();
|
||||
}
|
||||
m_pHdr->uiTxPos = uiTxPos;
|
||||
}
|
||||
|
||||
bool CMemBufferAccessorTx::TryWrite(const void* pData, uint32_t uiSize)
|
||||
{
|
||||
if (!IsValid())
|
||||
return false;
|
||||
|
||||
// pData is only allowed to be NULL when uiSize is zero
|
||||
if (uiSize && !pData)
|
||||
return false;
|
||||
|
||||
// Reserve a packet
|
||||
auto optPacket = Reserve(uiSize);
|
||||
if (!optPacket) return false;
|
||||
|
||||
// Copy the data
|
||||
if (optPacket->GetSize())
|
||||
std::copy(reinterpret_cast<const uint8_t*>(pData), reinterpret_cast<const uint8_t*>(pData) + uiSize, optPacket->GetDataPtr());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CAccessorRxPacket::CAccessorRxPacket(CMemBufferAccessorRx& rAccessor, CMemBufferAccessorBase::SPacketHdr* pPacketHdr) :
|
||||
m_pAccessor(&rAccessor)
|
||||
{
|
||||
// Checks
|
||||
if (!pPacketHdr) return;
|
||||
if (pPacketHdr->eType != CMemBufferAccessorBase::SPacketHdr::EType::data) return;
|
||||
if (pPacketHdr->eState != CMemBufferAccessorBase::SPacketHdr::EState::read) return;
|
||||
m_pPacketHdr = pPacketHdr;
|
||||
}
|
||||
|
||||
CAccessorRxPacket::CAccessorRxPacket(CAccessorRxPacket&& rpacket) noexcept:
|
||||
m_pAccessor(rpacket.m_pAccessor), m_pPacketHdr(rpacket.m_pPacketHdr)
|
||||
{
|
||||
rpacket.m_pAccessor = nullptr;
|
||||
rpacket.m_pPacketHdr = nullptr;
|
||||
}
|
||||
|
||||
CAccessorRxPacket& CAccessorRxPacket::operator=(CAccessorRxPacket&& rpacket) noexcept
|
||||
{
|
||||
m_pAccessor = rpacket.m_pAccessor;
|
||||
m_pPacketHdr = rpacket.m_pPacketHdr;
|
||||
rpacket.m_pAccessor = nullptr;
|
||||
rpacket.m_pPacketHdr = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAccessorRxPacket::operator bool() const
|
||||
{
|
||||
return IsValid();
|
||||
}
|
||||
|
||||
bool CAccessorRxPacket::IsValid() const
|
||||
{
|
||||
return GetData() && GetSize();
|
||||
}
|
||||
|
||||
void CAccessorRxPacket::Reset()
|
||||
{
|
||||
m_pPacketHdr = nullptr;
|
||||
}
|
||||
|
||||
uint32_t CAccessorRxPacket::GetSize() const
|
||||
{
|
||||
return m_pPacketHdr ? m_pPacketHdr->uiSize : 0;
|
||||
}
|
||||
|
||||
const uint8_t* CAccessorRxPacket::GetData() const
|
||||
{
|
||||
return reinterpret_cast<const uint8_t*>(m_pPacketHdr ? m_pPacketHdr + 1 : nullptr);
|
||||
}
|
||||
|
||||
void CAccessorRxPacket::Accept()
|
||||
{
|
||||
if (!m_pAccessor) return;
|
||||
|
||||
// Mark the packet as free
|
||||
std::unique_lock<std::mutex> lock(m_pAccessor->m_mtxReadAccess);
|
||||
if (m_pPacketHdr) m_pPacketHdr->eState = CMemBufferAccessorBase::SPacketHdr::EState::free;
|
||||
lock.unlock();
|
||||
|
||||
// Release any read packets
|
||||
m_pAccessor->ReleasePackets();
|
||||
}
|
||||
|
||||
void CMemBufferAccessorRx::Attach(uint8_t* pBuffer, uint32_t uiSize /*= 0*/)
|
||||
{
|
||||
// Restore from a possible previous connection.
|
||||
CMemBufferAccessorBase::Attach(pBuffer, uiSize);
|
||||
}
|
||||
|
||||
std::optional<CAccessorRxPacket> CMemBufferAccessorRx::TryRead()
|
||||
{
|
||||
if (!IsValid()) return {};
|
||||
|
||||
// Create a snapshot of the read and write positions
|
||||
std::unique_lock<std::mutex> lock(m_mtxReadAccess);
|
||||
uint32_t uiRxPos = m_pHdr->uiRxPos;
|
||||
uint32_t uiTxPos = m_pHdr->uiTxPos;
|
||||
|
||||
// Find the next available data packet that is unread
|
||||
while (uiRxPos != uiTxPos)
|
||||
{
|
||||
// Check for an invalid position
|
||||
if (uiRxPos > m_pHdr->uiSize)
|
||||
{
|
||||
// Should not happen!
|
||||
uiRxPos -= m_pHdr->uiSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is there still a header defined until the end of the buffer?
|
||||
if (m_pHdr->uiSize - uiRxPos < static_cast<uint32_t>(sizeof(SPacketHdr)))
|
||||
{
|
||||
// If the uiTxPos is equal or larger, no more header is available at the moment.
|
||||
if (uiTxPos >= uiRxPos) break;
|
||||
|
||||
// Start at the beginning
|
||||
uiRxPos = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the next packet
|
||||
SPacketHdr* pPacketHdr = GetPacketHdr(uiRxPos);
|
||||
|
||||
// Check the packet state
|
||||
enum class ENextStep {process, cancel, skip} eNextStep = ENextStep::cancel;
|
||||
switch (pPacketHdr->eState)
|
||||
{
|
||||
case SPacketHdr::EState::commit: // Packet is available and unread; process if packet is data packet.
|
||||
if (pPacketHdr->eType == SPacketHdr::EType::data)
|
||||
eNextStep = ENextStep::process;
|
||||
else
|
||||
eNextStep = ENextStep::skip;
|
||||
break;
|
||||
case SPacketHdr::EState::read: // Packet is in use by other thread. Skip this packet.
|
||||
case SPacketHdr::EState::free: // Packet is released, but TX has't been updated. Skip this packet.
|
||||
eNextStep = ENextStep::skip;
|
||||
break;
|
||||
case SPacketHdr::EState::reserved: // Packet is not finall written. This should not happen.
|
||||
default:
|
||||
eNextStep = ENextStep::cancel;
|
||||
break;
|
||||
}
|
||||
|
||||
// Do the next step
|
||||
if (eNextStep == ENextStep::cancel)
|
||||
break;
|
||||
if (eNextStep == ENextStep::skip)
|
||||
{
|
||||
// Update the read position
|
||||
uiRxPos = Align(static_cast<uint32_t>(reinterpret_cast<uint8_t*>(pPacketHdr) - m_pBuffer + sizeof(SPacketHdr))
|
||||
+ pPacketHdr->uiSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update packet header
|
||||
pPacketHdr->eState = SPacketHdr::EState::read;
|
||||
|
||||
// Return a packet class
|
||||
return CAccessorRxPacket(*this, pPacketHdr);
|
||||
}
|
||||
|
||||
// Packet not available.
|
||||
return {};
|
||||
}
|
||||
|
||||
void CMemBufferAccessorRx::ReleasePackets()
|
||||
{
|
||||
if (!IsValid()) return;
|
||||
|
||||
// Create a snapshot of the read and write positions
|
||||
std::unique_lock<std::mutex> lock(m_mtxReadAccess);
|
||||
uint32_t uiRxPos = m_pHdr->uiRxPos;
|
||||
uint32_t uiTxPos = m_pHdr->uiTxPos;
|
||||
|
||||
// Find the next available data packet that is unread
|
||||
while (uiRxPos != uiTxPos)
|
||||
{
|
||||
// Check for an invalid position
|
||||
if (uiRxPos > m_pHdr->uiSize)
|
||||
{
|
||||
// Should not happen!
|
||||
uiRxPos -= m_pHdr->uiSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is there still a header defined until the end of the buffer?
|
||||
if (m_pHdr->uiSize - uiRxPos < static_cast<uint32_t>(sizeof(SPacketHdr)))
|
||||
{
|
||||
// If the uiTxPos is equal or larger, no more header is available at the moment.
|
||||
if (uiTxPos >= uiRxPos) break;
|
||||
|
||||
// Start at the beginning
|
||||
uiRxPos = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the next packet
|
||||
SPacketHdr* pPacketHdr = GetPacketHdr(uiRxPos);
|
||||
|
||||
// Check whether the packet is an unread data packet
|
||||
if (pPacketHdr->eType == SPacketHdr::EType::data && pPacketHdr->eState != SPacketHdr::EState::free) break;
|
||||
|
||||
// Update the read position
|
||||
uiRxPos = Align(static_cast<uint32_t>(reinterpret_cast<uint8_t*>(pPacketHdr) - m_pBuffer + sizeof(SPacketHdr))
|
||||
+ pPacketHdr->uiSize);
|
||||
}
|
||||
|
||||
// Store the new position
|
||||
m_pHdr->uiRxPos = uiRxPos;
|
||||
|
||||
// Trigger
|
||||
TriggerDataReceive();
|
||||
}
|
||||
|
||||
CMemBufferAccessorBase::SPacketHdr* CMemBufferAccessorBase::GetPacketHdr(uint32_t uiPos) const
|
||||
{
|
||||
if (!m_pBuffer) return nullptr;
|
||||
return reinterpret_cast<SPacketHdr*>(m_pBuffer + uiPos);
|
||||
}
|
||||
|
||||
uint8_t* CMemBufferAccessorBase::GetPacketData(uint32_t uiPos) const
|
||||
{
|
||||
if (!m_pBuffer) return nullptr;
|
||||
return m_pBuffer + uiPos + static_cast<uint32_t>(sizeof(SPacketHdr));
|
||||
}
|
||||
|
||||
476
sdv_services/ipc_shared_mem/mem_buffer_accessor.h
Normal file
476
sdv_services/ipc_shared_mem/mem_buffer_accessor.h
Normal file
@@ -0,0 +1,476 @@
|
||||
#ifndef MEM_BUFFER_ACCESSOR_H
|
||||
#define MEM_BUFFER_ACCESSOR_H
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <optional>
|
||||
#include <interfaces/core_types.h>
|
||||
|
||||
/**
|
||||
* @brief Accessor direction
|
||||
*/
|
||||
enum class EAccessType
|
||||
{
|
||||
rx, ///< Reading accessor
|
||||
tx, ///< Writing accessor
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Memory buffer accessor.
|
||||
* @details Lock-free memory buffer accessor. This buffer allows the writing and reading of memory packets in a circular buffer.
|
||||
* @attention This class does not synchronize calls! It works if only one thread at the time writes data and only one thread at
|
||||
* the time reads data. Reading and writing can occur simulateously.
|
||||
* @attention No protection is implemented to monitor the lifetime of the buffer. It is assumed that during any call this buffer
|
||||
* is valid.
|
||||
* @remarks This class doesn't provide any handshaking. This is the task of the objects using this class.
|
||||
*/
|
||||
class CMemBufferAccessorBase
|
||||
{
|
||||
protected:
|
||||
/**
|
||||
* @brief Memory buffer header
|
||||
*/
|
||||
struct SBufferHdr
|
||||
{
|
||||
uint32_t uiVersion = SDVFrameworkInterfaceVersion; ///< Check for compatible communication channel
|
||||
uint32_t uiSize = 0u; ///< The size of the buffer
|
||||
uint32_t uiTxPos = 0u; ///< Current write position
|
||||
uint32_t uiRxPos = 0u; ///< Current read position
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Packet header
|
||||
* @details The packet header is placed in front of every packet and describes the packet type as well as the size of the
|
||||
* packet.
|
||||
* @remarks Packets are always starting at a 64-bit boundary.
|
||||
*/
|
||||
struct SPacketHdr
|
||||
{
|
||||
/**
|
||||
* @brief Packet types
|
||||
*/
|
||||
enum class EType : uint16_t
|
||||
{
|
||||
data = 0, ///< The packet contains data
|
||||
stuffing = 1, ///< The packet doesn't contain data and is used to fill up space
|
||||
} eType; ///< Packet type
|
||||
|
||||
/**
|
||||
* @brief Packet state
|
||||
*/
|
||||
enum class EState : uint16_t
|
||||
{
|
||||
free = 0, ///< Packet is not reserved; overwriting is allowed
|
||||
reserved = 1, ///< Space is reserved but not committed.
|
||||
commit = 2, ///< Packet is committed and available for reading.
|
||||
read = 3, ///< Packet is currently being read.
|
||||
} eState; ///< The packet state
|
||||
|
||||
uint32_t uiSize; ///< Packet size
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CMemBufferAccessorBase() = default;
|
||||
|
||||
/**
|
||||
* @brief Attach the buffer to the accessor.
|
||||
* @param[in] pBuffer Pointer to the buffer that should be accessed.
|
||||
* @param[in] uiSize Size of the buffer when still uninitialized (during buffer creation); otherwise 0.
|
||||
*/
|
||||
virtual void Attach(uint8_t* pBuffer, uint32_t uiSize = 0);
|
||||
|
||||
/**
|
||||
* @brief Detach the buffer from the accessor.
|
||||
*/
|
||||
virtual void Detach();
|
||||
|
||||
/**
|
||||
* @brief Is valid.
|
||||
* @return Returns 'true' when valid; otherwise 'false'.
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* @brief Get the buffer pointer.
|
||||
* @return Returns a pointer to the buffer or NULL when there is no buffer.
|
||||
*/
|
||||
const uint8_t* GetBufferPointer() const;
|
||||
|
||||
/**
|
||||
* @brief Reset the Rx position to skip any sent data. Typically needed when a reconnect should take place.
|
||||
*/
|
||||
void ResetRx() { if (m_pHdr) m_pHdr->uiRxPos = m_pHdr->uiTxPos; m_bCancel = false; }
|
||||
|
||||
/**
|
||||
* @brief Cancel send operation.
|
||||
*/
|
||||
void CancelSend() { m_bCancel = true; TriggerDataReceive(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); }
|
||||
|
||||
/**
|
||||
* @brief Canceled?
|
||||
* @return Returns 'true' when the currend send job has been canceled; 'false' when not.
|
||||
*/
|
||||
bool Canceled() const { return m_bCancel; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Providing the position, return the packet header preceding the data.
|
||||
* @param[in] uiPos The position in the buffer
|
||||
* @return Pointer to the packet header or NULL when the position isn't within the buffer.
|
||||
*/
|
||||
SPacketHdr* GetPacketHdr(uint32_t uiPos) const;
|
||||
|
||||
/**
|
||||
* @brief Providing the position, return the packet data following the header.
|
||||
* @param[in] uiPos The position in the buffer
|
||||
* @return Pointer to the packet data or NULL when the position isn't within the buffer.
|
||||
*/
|
||||
uint8_t* GetPacketData(uint32_t uiPos) const;
|
||||
|
||||
/**
|
||||
* @brief Has unread data.
|
||||
* @return Returns 'true' when unread data is still available; otherwise returns 'false'.
|
||||
*/
|
||||
virtual bool HasUnreadData() const { return m_pHdr && m_pHdr->uiRxPos != m_pHdr->uiTxPos; }
|
||||
|
||||
/**
|
||||
* @brief Trigger listener that a write operation was completed.
|
||||
*/
|
||||
virtual void TriggerDataSend() = 0;
|
||||
|
||||
/**
|
||||
* @brief Wait for a write operation to be completed.
|
||||
* @param[in] uiTimeoutMs The amount of time (in ms) to wait for a trigger.
|
||||
* @return Returns 'true' when data was stored, 'false' when a timeout occurred.
|
||||
*/
|
||||
virtual bool WaitForData(uint32_t uiTimeoutMs) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Trigger listener that a read operation was completed.
|
||||
*/
|
||||
virtual void TriggerDataReceive() = 0;
|
||||
|
||||
/**
|
||||
* @brief Wait for a read operation to be completed.
|
||||
* @param[in] uiTimeoutMs The amount of time (in ms) to wait for a trigger.
|
||||
* @return Returns 'true' when data was stored, 'false' when a timeout occurred.
|
||||
*/
|
||||
virtual bool WaitForFreeSpace(uint32_t uiTimeoutMs) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Align to 64 bits
|
||||
* @tparam T The type of the size argument.
|
||||
* @param[in] tSize The current size.
|
||||
* @return The 64-bit aligned size.
|
||||
*/
|
||||
template <typename T>
|
||||
inline uint32_t Align(T tSize)
|
||||
{
|
||||
uint32_t uiSize = static_cast<uint32_t>(tSize);
|
||||
return (uiSize % 8) ? uiSize + 8 - uiSize % 8 : uiSize;
|
||||
}
|
||||
|
||||
uint8_t* m_pBuffer = nullptr; ///< Buffer pointer
|
||||
SBufferHdr* m_pHdr = nullptr; ///< Buffer header
|
||||
bool m_bCancel = false; ///< Cancel the send operation
|
||||
};
|
||||
|
||||
// Forward declaration
|
||||
class CMemBufferAccessorTx;
|
||||
|
||||
/**
|
||||
* @brief Tx access can be done randomly. To prevent shutting down while packets are reserved but not committed, the accessor
|
||||
* packet take over the management of this.
|
||||
*/
|
||||
class CAccessorTxPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CAccessorTxPacket() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rAccessor Reference to the accessor managing the buffer.
|
||||
* @param[in] pPacketHdr Pointer to the packet header.
|
||||
*/
|
||||
CAccessorTxPacket(CMemBufferAccessorTx& rAccessor, CMemBufferAccessorBase::SPacketHdr* pPacketHdr);
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is deleted.
|
||||
* @param[in] rpacket Reference to the packet to copy from.
|
||||
*/
|
||||
CAccessorTxPacket(const CAccessorTxPacket& rpacket) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
* @param[in] rpacket Reference to the packet to move from.
|
||||
*/
|
||||
CAccessorTxPacket(CAccessorTxPacket&& rpacket) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Destructor - will automatically commit the packet if not done so already.
|
||||
*/
|
||||
~CAccessorTxPacket();
|
||||
|
||||
/**
|
||||
* @brief Copy operator is deleted.
|
||||
* @param[in] rpacket Reference to the packet to copy from.
|
||||
* @return Reference to this packet class.
|
||||
*/
|
||||
CAccessorTxPacket& operator=(const CAccessorTxPacket& rpacket) = delete;
|
||||
|
||||
/**
|
||||
* @brief Copy operator is deleted.
|
||||
* @param[in] rpacket Reference to the packet to copy from.
|
||||
* @return Reference to this packet class.
|
||||
*/
|
||||
CAccessorTxPacket& operator=(CAccessorTxPacket&& rpacket) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Cast operator used to check validity.
|
||||
*/
|
||||
operator bool() const;
|
||||
|
||||
/**
|
||||
* @brief Does the packet contain valid data (data is not committed yet).
|
||||
* @return The validity of the packet.
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* @brief Commit the data. This will invalidate the content.
|
||||
*/
|
||||
void Commit();
|
||||
|
||||
/**
|
||||
* @brief Get the size of the packet data.
|
||||
* @return The packet size; could be 0 when no data was allocated.
|
||||
*/
|
||||
uint32_t GetSize() const;
|
||||
|
||||
/**
|
||||
* @brief Get the packet pointer.
|
||||
* @return Pointer to the buffer holding the packet data or nullptr when there is no data allocated or the packet has been
|
||||
* committed.
|
||||
*/
|
||||
uint8_t* GetDataPtr();
|
||||
|
||||
/**
|
||||
* @brief Templated version of GetData.
|
||||
* @tparam TData The type to cast the data pointer to.
|
||||
* @param[in] uiOffset The offset to use in bytes (default no offset).
|
||||
* @return Pointer to the data or NULL when the data doesn't fit in the buffer.
|
||||
*/
|
||||
template <typename TData>
|
||||
TData* GetDataPtr(uint32_t uiOffset = 0)
|
||||
{
|
||||
return GetSize() >= (uiOffset + sizeof(TData)) ? reinterpret_cast<TData*>(GetDataPtr()) : nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
CMemBufferAccessorTx* m_pAccessor = nullptr; ///< Pointer to the accessor.
|
||||
CMemBufferAccessorBase::SPacketHdr* m_pPacketHdr = nullptr; ///< Packet header.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Accessor TX implementation
|
||||
*/
|
||||
class CMemBufferAccessorTx : public CMemBufferAccessorBase
|
||||
{
|
||||
friend CAccessorTxPacket;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CMemBufferAccessorTx();
|
||||
|
||||
/**
|
||||
* @brief Reserve memory for writing to the buffer without an extra copy.
|
||||
* @param[in] uiSize Size of the memory block to reserve. Must be smaller than the buffer size.
|
||||
* @param[in] uiTimeoutMs The amount of time (in ms) to wait for a free buffer. Default 1000 ms (needed when allocating large
|
||||
* chunks of data that need to be allocated on the receiving side).
|
||||
* @return Returns a packet object if successful or an empty option when a timeout occurred.
|
||||
* @attention The reader will only be able to continue reading after a commit. If, for some reason, there is no commit, the
|
||||
* reader won't be able to retrieve any more data.
|
||||
* @attention Performance is poor if the size is close to the buffer size. As a rule, uiSize should be at the most 1/4th of
|
||||
* the buffer size.
|
||||
*/
|
||||
std::optional<CAccessorTxPacket> Reserve(uint32_t uiSize, uint32_t uiTimeoutMs = 1000);
|
||||
|
||||
/**
|
||||
* @brief Write data to the buffer.
|
||||
* @param[in] pData Pointer to the data to write.
|
||||
* @param[in] uiSize Length of the data in bytes.
|
||||
* @return Returns 'true' if writing was successful or 'false' if the packet was invalid or (currently) doesn't fit into the
|
||||
* buffer.
|
||||
*/
|
||||
bool TryWrite(const void* pData, uint32_t uiSize);
|
||||
|
||||
/**
|
||||
* @brief Get the accessor access type.
|
||||
* @return The access type of this accessor.
|
||||
*/
|
||||
static constexpr EAccessType GetAccessType() { return EAccessType::tx; };
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Commit the writing to the buffer.
|
||||
* @param[in] pPacketHdr Pointer to the previously reserved packet header.
|
||||
*/
|
||||
void Commit(SPacketHdr* pPacketHdr);
|
||||
|
||||
bool m_bBlockReserve = false; ///< When set, do not reserve any more packets.
|
||||
std::mutex m_mtxReservedPackes; ///< Access protection for the reserved access queue.
|
||||
std::deque<SPacketHdr*> m_queueReservedPackets; ///< Queue containing the currently reserved packets.
|
||||
};
|
||||
|
||||
// Forward declaration
|
||||
class CMemBufferAccessorRx;
|
||||
|
||||
/**
|
||||
* @brief Rx access can be done randomly. To ensure the the data is released properly, the accessor packet is returned to manage
|
||||
* this.
|
||||
*/
|
||||
class CAccessorRxPacket
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CAccessorRxPacket() = default;
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param[in] rAccessor Reference to the accessor managing the buffer.
|
||||
* @param[in] pPacketHdr Pointer to the packet header.
|
||||
*/
|
||||
CAccessorRxPacket(CMemBufferAccessorRx& rAccessor, CMemBufferAccessorBase::SPacketHdr* pPacketHdr);
|
||||
|
||||
/**
|
||||
* @brief Copy constructor is deleted.
|
||||
* @param[in] rpacket Reference to the packet to copy from.
|
||||
*/
|
||||
CAccessorRxPacket(const CAccessorRxPacket& rpacket) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
* @param[in] rpacket Reference to the packet to move from.
|
||||
*/
|
||||
CAccessorRxPacket(CAccessorRxPacket&& rpacket) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Copy operator is deleted.
|
||||
* @param[in] rpacket Reference to the packet to copy from.
|
||||
* @return Reference to this packet class.
|
||||
*/
|
||||
CAccessorRxPacket& operator=(const CAccessorRxPacket& rpacket) = delete;
|
||||
|
||||
/**
|
||||
* @brief Copy operator is deleted.
|
||||
* @param[in] rpacket Reference to the packet to copy from.
|
||||
* @return Reference to this packet class.
|
||||
*/
|
||||
CAccessorRxPacket& operator=(CAccessorRxPacket&& rpacket) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Cast operator used to check validity.
|
||||
*/
|
||||
operator bool() const;
|
||||
|
||||
/**
|
||||
* @brief Does the packet contain valid data (data pointer is available and the size is not zero).
|
||||
* @return The validity of the packet.
|
||||
*/
|
||||
bool IsValid() const;
|
||||
|
||||
/**
|
||||
* @brief Reset the packet (not accepting the content). Packet will be available again for next request.
|
||||
*/
|
||||
void Reset();
|
||||
|
||||
/**
|
||||
* @brief Get the size of the packet data.
|
||||
* @return The packet size.
|
||||
*/
|
||||
uint32_t GetSize() const;
|
||||
|
||||
/**
|
||||
* @brief Get the packet pointer.
|
||||
* @return Pointer to the buffer holding the packet data or nullptr when the packet is cleared.
|
||||
*/
|
||||
const uint8_t* GetData() const;
|
||||
|
||||
/**
|
||||
* @brief Templated version of GetData.
|
||||
* @tparam TData The type to cast the data pointer to.
|
||||
* @param[in] uiOffset The offset to use in bytes (default no offset).
|
||||
* @return Pointer to the data or NULL when the data doesn't fit in the buffer.
|
||||
*/
|
||||
template <typename TData>
|
||||
const TData* GetData(uint32_t uiOffset = 0) const
|
||||
{
|
||||
return GetSize() >= (uiOffset + sizeof(TData)) ? reinterpret_cast<const TData*>(GetData()) : nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Accept the packet; packet will be released for new packet data.
|
||||
*/
|
||||
void Accept();
|
||||
|
||||
private:
|
||||
CMemBufferAccessorRx* m_pAccessor = nullptr; ///< Pointer to the accessor.
|
||||
CMemBufferAccessorBase::SPacketHdr* m_pPacketHdr = nullptr; ///< Packet header.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Accessor RX implementation
|
||||
*/
|
||||
class CMemBufferAccessorRx : public CMemBufferAccessorBase
|
||||
{
|
||||
friend CAccessorRxPacket;
|
||||
public:
|
||||
// Suppress cppcheck warning about a useless override. The function is here for clearer code documentation.
|
||||
// cppcheck-suppress uselessOverride
|
||||
/**
|
||||
* @brief Attach the buffer to the accessor. Overload of CMemBufferAccessorBase::Attach.
|
||||
* @param[in] pBuffer Pointer to the buffer that should be accessed.
|
||||
* @param[in] uiSize Size of the buffer when still uninitialized (during buffer creation); otherwise 0.
|
||||
*/
|
||||
virtual void Attach(uint8_t* pBuffer, uint32_t uiSize = 0) override;
|
||||
|
||||
/**
|
||||
* @brief Get a packet. If the packet is accepted, call release before the packet
|
||||
* @return Returns 'true' when the access request was successful or 'false' when currently there is no data.
|
||||
* @attention The writer will only be able to overwrite previously read data after it is released. If, for some reason there is
|
||||
* no release, the writer won't be able to write past the accessed packet.
|
||||
*/
|
||||
std::optional<CAccessorRxPacket> TryRead();
|
||||
|
||||
/**
|
||||
* @brief Get the accessor access type.
|
||||
* @return The access type of this accessor.
|
||||
*/
|
||||
static constexpr EAccessType GetAccessType() { return EAccessType::rx; };
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Release any read packets and update the read pointer.
|
||||
*/
|
||||
void ReleasePackets();
|
||||
|
||||
private:
|
||||
std::mutex m_mtxReadAccess; ///< Synchronize read access.
|
||||
};
|
||||
|
||||
#endif // !defined MEM_BUFFER_ACCESSOR_H
|
||||
466
sdv_services/ipc_shared_mem/shared_mem_buffer_posix.h
Normal file
466
sdv_services/ipc_shared_mem/shared_mem_buffer_posix.h
Normal file
@@ -0,0 +1,466 @@
|
||||
#if !defined POSIX_SHARED_MEM_BUFFER_H && defined __unix__
|
||||
#define POSIX_SHARED_MEM_BUFFER_H
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <semaphore.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <support/toml.h>
|
||||
#include "../../global/trace.h"
|
||||
#include "mem_buffer_accessor.h"
|
||||
|
||||
/**
|
||||
* @brief In-process memory buffer.
|
||||
*/
|
||||
template <class TAccessor>
|
||||
class CSharedMemBuffer : public TAccessor
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
* @param[in] uiSize Optional size of the buffer. If zero, a default buffer size of 10k is configured.
|
||||
* @param[in] rssName Optional name to be used for the connection. If empty, a random name is generated.
|
||||
* @param[in] bServer Optional boolean indicating whether the connection is a server (true), which initiates the connection, or
|
||||
* a client (false), which opens an existing connection.
|
||||
*/
|
||||
CSharedMemBuffer(uint32_t uiSize = 0, const std::string& rssName = std::string(), bool bServer = true);
|
||||
|
||||
/**
|
||||
* @brief Connection constructor
|
||||
* @param[in] rssConnectionString Reference to string with connection information.
|
||||
*/
|
||||
CSharedMemBuffer(const std::string& rssConnectionString);
|
||||
|
||||
/** No copy constructor */
|
||||
CSharedMemBuffer(const CSharedMemBuffer&) = delete;
|
||||
|
||||
/** No move constructor */
|
||||
CSharedMemBuffer(CSharedMemBuffer&&) = delete;
|
||||
|
||||
/**
|
||||
* \brief Default destructor
|
||||
*/
|
||||
~CSharedMemBuffer();
|
||||
|
||||
/** No copy-assignment operator */
|
||||
CSharedMemBuffer& operator=(const CSharedMemBuffer&) = delete;
|
||||
|
||||
/** No move-assignment operator */
|
||||
CSharedMemBuffer& operator=(CSharedMemBuffer&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Detach the buffer. Overload of CMemBufferAccessorBase::Detach.
|
||||
* @details The detach function detaches the shared memory without deleting the memory. This keeps the memory alive for reuse.
|
||||
*/
|
||||
virtual void Detach() override;
|
||||
|
||||
/**
|
||||
* @brief Return the connection string to connect to this shared memory.
|
||||
* @return The connection string to connect to this buffer.
|
||||
*/
|
||||
std::string GetConnectionString() const;
|
||||
|
||||
/**
|
||||
* @brief Trigger listener that a write operation was completed.
|
||||
*/
|
||||
void TriggerDataSend() override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a write operation to be completed.
|
||||
* @param[in] uiTimeoutMs The amount of time (in ms) to wait for a trigger.
|
||||
* @return Returns 'true' when data was stored, 'false' when a timeout occurred.
|
||||
*/
|
||||
bool WaitForData(uint32_t uiTimeoutMs) const override;
|
||||
|
||||
/**
|
||||
* @brief Trigger listener that a read operation was completed.
|
||||
*/
|
||||
void TriggerDataReceive() override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a read operation to be completed.
|
||||
* @param[in] uiTimeoutMs The amount of time (in ms) to wait for a trigger.
|
||||
* @return Returns 'true' when data was stored, 'false' when a timeout occurred.
|
||||
*/
|
||||
bool WaitForFreeSpace(uint32_t uiTimeoutMs) const override;
|
||||
|
||||
/**
|
||||
* @brief Return the last reported error.
|
||||
* @return Error string.
|
||||
*/
|
||||
std::string GetError() const { return m_ssError; }
|
||||
|
||||
/**
|
||||
* @brief Get the size of the buffer.
|
||||
* @return Returns the size of the buffer.
|
||||
*/
|
||||
uint32_t GetSize() const { return m_uiSize; }
|
||||
|
||||
/**
|
||||
* @brief Get the name of the buffer.
|
||||
* @return Returns the name of the buffer.
|
||||
*/
|
||||
std::string GetName() const { return m_ssName; }
|
||||
|
||||
private:
|
||||
uint32_t m_uiSize = 0u; ///< Size of the shared memory buffer.
|
||||
int m_iFileDescr = 0; ///< File descriptor of the shared memory.
|
||||
std::string m_ssName; ///< Name of the shared memory.
|
||||
uint8_t* m_pBuffer = nullptr; ///< Pointer to the mapped buffer.
|
||||
std::string m_ssSyncTx; ///< Name of the signalling event.
|
||||
sem_t* m_pSemaphoreTx = nullptr; ///< Semaphore to trigger written.
|
||||
std::string m_ssSyncRx; ///< Name of the signalling event.
|
||||
sem_t* m_pSemaphoreRx = nullptr; ///< Semaphore to trigger written.
|
||||
std::string m_ssError; ///< The last reported error.
|
||||
bool m_bServer = false; ///< Set when the shared memory is configured as server. Otherwise as client.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Shared memory buffer used for reading.
|
||||
*/
|
||||
using CSharedMemBufferRx = CSharedMemBuffer<CMemBufferAccessorRx>;
|
||||
|
||||
/**
|
||||
* @brief Shared memory buffer used for writing.
|
||||
*/
|
||||
using CSharedMemBufferTx = CSharedMemBuffer<CMemBufferAccessorTx>;
|
||||
|
||||
template <class TAccessor>
|
||||
inline CSharedMemBuffer<TAccessor>::CSharedMemBuffer(uint32_t uiSize /*= 0*/, const std::string& rssName /*= std::string()*/,
|
||||
bool bServer /*= true*/) : m_uiSize(bServer ? (uiSize ? uiSize : 128 * 1024) : 0), m_bServer(bServer)
|
||||
{
|
||||
// Create a name to be used in the connection string
|
||||
std::string ssDirectionString;
|
||||
if (bServer)
|
||||
ssDirectionString = TAccessor::GetAccessType() == EAccessType::rx ? "RESPONSE_" : "REQUEST_";
|
||||
else
|
||||
ssDirectionString = TAccessor::GetAccessType() == EAccessType::rx ? "REQUEST_" : "RESPONSE_";
|
||||
if (!rssName.empty())
|
||||
{
|
||||
m_ssName = std::string("SDV_SHARED_") + ssDirectionString + rssName;
|
||||
m_ssSyncTx = std::string("SDV_TX_SYNC_") + ssDirectionString + rssName;
|
||||
m_ssSyncRx = std::string("SDV_RX_SYNC_") + ssDirectionString + rssName;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t uiCnt = std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
||||
m_ssName = std::string("SDV_SHARED_") + ssDirectionString + std::to_string(uiCnt);
|
||||
m_ssSyncTx = std::string("SDV_TX_SYNC_") + ssDirectionString + std::to_string(uiCnt);
|
||||
m_ssSyncRx = std::string("SDV_RX_SYNC_") + ssDirectionString + std::to_string(uiCnt);
|
||||
}
|
||||
|
||||
// Create a path
|
||||
std::string ssNamePath = "/" + m_ssName;
|
||||
// std::string ssSyncTxPath = "/" + m_ssSyncTx;
|
||||
// std::string ssSyncRxPath = "/" + m_ssSyncRx;
|
||||
|
||||
// Unlink just in case the last server had crashed and the mapping still exists.
|
||||
if (m_bServer)
|
||||
{
|
||||
shm_unlink((std::string("/") + m_ssName).c_str());
|
||||
sem_unlink(m_ssSyncTx.c_str());
|
||||
sem_unlink(m_ssSyncRx.c_str());
|
||||
}
|
||||
|
||||
// Initialize the semaphores
|
||||
if (bServer)
|
||||
{
|
||||
m_pSemaphoreTx = sem_open(m_ssSyncTx.c_str(), O_CREAT | O_EXCL, 0777 /*O_RDWR*/, 0);
|
||||
if (!m_pSemaphoreTx || m_pSemaphoreTx == SEM_FAILED)
|
||||
{
|
||||
m_ssError = "Failed to create new semaphore " + m_ssSyncTx + ".";
|
||||
return;
|
||||
}
|
||||
m_pSemaphoreRx = sem_open(m_ssSyncRx.c_str(), O_CREAT | O_EXCL, 0777 /*O_RDWR*/, 0);
|
||||
if (!m_pSemaphoreRx || m_pSemaphoreRx == SEM_FAILED)
|
||||
{
|
||||
m_ssError = "Failed to create new semaphore " + m_ssSyncRx + ".";
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pSemaphoreTx = sem_open(m_ssSyncTx.c_str(), 0);
|
||||
if (!m_pSemaphoreTx || m_pSemaphoreTx == SEM_FAILED)
|
||||
{
|
||||
m_ssError = "Failed to open existing semaphore " + m_ssSyncTx + ".";
|
||||
return;
|
||||
}
|
||||
m_pSemaphoreRx = sem_open(m_ssSyncRx.c_str(), 0);
|
||||
if (!m_pSemaphoreRx || m_pSemaphoreRx == SEM_FAILED)
|
||||
{
|
||||
m_ssError = "Failed to open existing semaphore " + m_ssSyncRx + ".";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get shared memory file descriptor (NOT a file)
|
||||
if (bServer)
|
||||
{
|
||||
m_iFileDescr = shm_open(ssNamePath.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
|
||||
if (m_iFileDescr == -1)
|
||||
{
|
||||
m_ssError = "Failed to create the shared memory file descriptor " + ssNamePath + ".";
|
||||
return;
|
||||
}
|
||||
|
||||
// Extend shared memory object as by default it's initialized with size 0
|
||||
int iResult = ftruncate(m_iFileDescr, m_uiSize);
|
||||
if (iResult == -1)
|
||||
{
|
||||
m_ssError = "Failed to extend the shared memory.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_iFileDescr = shm_open(ssNamePath.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
|
||||
if (m_iFileDescr == -1)
|
||||
{
|
||||
m_ssError = "Failed to open the shared memory file descriptor " + ssNamePath + ".";
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the size of the shared memory
|
||||
struct stat sMemInfo{};
|
||||
if (fstat(m_iFileDescr, &sMemInfo) == -1 || !sMemInfo.st_size)
|
||||
{
|
||||
m_ssError = "Failed to request the size of the shared memory file descriptor " + ssNamePath + ".";
|
||||
return;
|
||||
}
|
||||
m_uiSize = static_cast<uint32_t>(sMemInfo.st_size);
|
||||
}
|
||||
|
||||
// map shared memory to process address space
|
||||
m_pBuffer = reinterpret_cast<uint8_t*>(mmap(NULL, m_uiSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_iFileDescr, 0));
|
||||
if (!m_pBuffer || m_pBuffer == MAP_FAILED)
|
||||
{
|
||||
m_ssError = "Failed to map the shared memory in process address space.";
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is a server, the size causes the initialization. For a client, no initialization should take place (the server has
|
||||
// done so already).
|
||||
TAccessor::Attach(m_pBuffer, bServer ? m_uiSize : 0);
|
||||
|
||||
TRACE("Accessed shared memory for ", m_ssSyncTx, " and ", m_ssSyncRx, ".");
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline CSharedMemBuffer<TAccessor>::CSharedMemBuffer(const std::string& rssConnectionString)
|
||||
{
|
||||
if (rssConnectionString.empty())
|
||||
{
|
||||
m_ssError = "Missing connection string.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Interpret the connection string
|
||||
sdv::toml::CTOMLParser config(rssConnectionString);
|
||||
|
||||
// The connection string can contain multiple parameters. Search for the first parameters fitting the accessor direction
|
||||
size_t nIndex = 0;
|
||||
sdv::toml::CNodeCollection nodeConnectParamCollection = config.GetDirect("ConnectParam");
|
||||
do
|
||||
{
|
||||
sdv::toml::CNodeCollection nodeConnectParam;
|
||||
switch (nodeConnectParamCollection.GetType())
|
||||
{
|
||||
case sdv::toml::ENodeType::node_array:
|
||||
if (nIndex >= nodeConnectParamCollection.GetCount()) break;
|
||||
nodeConnectParam = nodeConnectParamCollection[nIndex];
|
||||
break;
|
||||
case sdv::toml::ENodeType::node_table:
|
||||
if (nIndex > 0) break;
|
||||
nodeConnectParam = nodeConnectParamCollection;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (nodeConnectParam.GetType() != sdv::toml::ENodeType::node_table) break;
|
||||
|
||||
nIndex++;
|
||||
|
||||
// Check for shared memory
|
||||
if (nodeConnectParam.GetDirect("Type").GetValue() != "shared_mem") continue;
|
||||
|
||||
// Check the direction
|
||||
if (nodeConnectParam.GetDirect("Direction").GetValue() !=
|
||||
(TAccessor::GetAccessType() == EAccessType::rx ? "response" : "request"))
|
||||
continue;
|
||||
|
||||
// Get the information
|
||||
m_ssName = static_cast<std::string>(nodeConnectParam.GetDirect("Location").GetValue());
|
||||
m_ssSyncTx = static_cast<std::string>(nodeConnectParam.GetDirect("SyncTx").GetValue());
|
||||
m_ssSyncRx = static_cast<std::string>(nodeConnectParam.GetDirect("SyncRx").GetValue());
|
||||
break;
|
||||
} while (true);
|
||||
if (m_ssName.empty() || m_ssSyncTx.empty() || m_ssSyncRx.empty())
|
||||
{
|
||||
m_ssError = "Incomplete connection information.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a path
|
||||
std::string ssPath = "/" + m_ssName;
|
||||
// std::string ssSyncTxPath = "/" + m_ssSyncTx;
|
||||
// std::string ssSyncRxPath = "/" + m_ssSyncRx;
|
||||
|
||||
// Get shared memory file descriptor (NOT a file)
|
||||
m_iFileDescr = shm_open(ssPath.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
|
||||
if (m_iFileDescr == -1)
|
||||
{
|
||||
m_ssError = "Failed to open the shared memory file descriptor " + ssPath + ".";
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the size of the shared memory
|
||||
struct stat sMemInfo{};
|
||||
if (fstat(m_iFileDescr, &sMemInfo) == -1 || !sMemInfo.st_size)
|
||||
{
|
||||
m_ssError = "Failed to request the size of the shared memory file descriptor " + ssPath + ".";
|
||||
return;
|
||||
}
|
||||
m_uiSize = static_cast<uint32_t>(sMemInfo.st_size);
|
||||
|
||||
// Map shared memory to process address space
|
||||
m_pBuffer = reinterpret_cast<uint8_t*>(mmap(NULL, m_uiSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_iFileDescr, 0));
|
||||
if (!m_pBuffer || m_pBuffer == MAP_FAILED)
|
||||
{
|
||||
m_ssError = "Failed to map the shared memory in process address space.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the semaphore
|
||||
m_pSemaphoreTx = sem_open(m_ssSyncTx.c_str(), 0);
|
||||
if (!m_pSemaphoreTx || m_pSemaphoreTx == SEM_FAILED)
|
||||
{
|
||||
m_ssError = "Failed to open existing semaphore " + m_ssSyncTx + ".";
|
||||
return;
|
||||
}
|
||||
m_pSemaphoreRx = sem_open(m_ssSyncRx.c_str(), 0);
|
||||
if (!m_pSemaphoreRx || m_pSemaphoreRx == SEM_FAILED)
|
||||
{
|
||||
m_ssError = "Failed to open existing semaphore " + m_ssSyncRx + ".";
|
||||
return;
|
||||
}
|
||||
|
||||
TAccessor::Attach(m_pBuffer);
|
||||
|
||||
TRACE("Opened shared memory for ", m_ssSyncTx, " and ", m_ssSyncRx, ".");
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
CSharedMemBuffer<TAccessor>::~CSharedMemBuffer()
|
||||
{
|
||||
// ATTENTION unmapping and unlinking will remove any connection to the shared memory within this process. When multiple
|
||||
// accessors are used, this will invalidate them immediately.
|
||||
if (m_pBuffer && m_pBuffer != MAP_FAILED)
|
||||
munmap(m_pBuffer, m_uiSize);
|
||||
if (m_bServer && !m_ssName.empty())
|
||||
shm_unlink((std::string("/") + m_ssName).c_str());
|
||||
if (m_bServer && m_pSemaphoreTx)
|
||||
sem_unlink(m_ssSyncTx.c_str());
|
||||
if (m_bServer && m_pSemaphoreRx)
|
||||
sem_unlink(m_ssSyncRx.c_str());
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
void CSharedMemBuffer<TAccessor>::Detach()
|
||||
{
|
||||
m_uiSize = 0u;
|
||||
m_iFileDescr = 0;
|
||||
m_ssName.clear();
|
||||
m_pBuffer = nullptr;
|
||||
m_ssSyncTx.clear();
|
||||
m_pSemaphoreTx = nullptr;
|
||||
m_ssSyncRx.clear();
|
||||
m_pSemaphoreRx = nullptr;
|
||||
m_ssError.clear();
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline std::string CSharedMemBuffer<TAccessor>::GetConnectionString() const
|
||||
{
|
||||
// The connection string contains the TOML file for connecting to this shared memory.
|
||||
std::stringstream sstream;
|
||||
sstream << "[[ConnectParam]]" << std::endl;
|
||||
sstream << "Type = \"shared_mem\"" << std::endl;
|
||||
sstream << "Location = \"" << m_ssName << "\"" << std::endl;
|
||||
sstream << "SyncTx = \"" << m_ssSyncTx << "\"" << std::endl;
|
||||
sstream << "SyncRx = \"" << m_ssSyncRx << "\"" << std::endl;
|
||||
// The target direction is the opposite of the direction of the accessor. Therefore, if the accessor uses an RX access type,
|
||||
// the target uses an TX access type and should be configured as response, otherwise it is a request.
|
||||
sstream << "Direction = \"" << (TAccessor::GetAccessType() == EAccessType::rx ? "request" : "response") << "\"" << std::endl;
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline void CSharedMemBuffer<TAccessor>::TriggerDataSend()
|
||||
{
|
||||
if (!m_pSemaphoreTx || m_pSemaphoreTx == SEM_FAILED)
|
||||
return;
|
||||
sem_post(m_pSemaphoreTx);
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline bool CSharedMemBuffer<TAccessor>::WaitForData(uint32_t uiTimeoutMs) const
|
||||
{
|
||||
if (!m_pSemaphoreTx || m_pSemaphoreTx == SEM_FAILED)
|
||||
return false;
|
||||
|
||||
// Check whether there is data; if so, return true.
|
||||
if (TAccessor::HasUnreadData())
|
||||
return true;
|
||||
|
||||
// Get the time from the realtime clock
|
||||
timespec sTimespec{};
|
||||
if (clock_gettime(CLOCK_REALTIME, &sTimespec) == -1)
|
||||
return false;
|
||||
uint64_t uiTimeNs = sTimespec.tv_nsec + uiTimeoutMs * 1000000ull;
|
||||
sTimespec.tv_nsec = uiTimeNs % 1000000000ull;
|
||||
sTimespec.tv_sec += uiTimeNs / 1000000000ull;
|
||||
|
||||
// Wait for the semaphore
|
||||
int iResult = sem_timedwait(m_pSemaphoreTx, &sTimespec);
|
||||
if (iResult < 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline void CSharedMemBuffer<TAccessor>::TriggerDataReceive()
|
||||
{
|
||||
if (!m_pSemaphoreRx || m_pSemaphoreRx == SEM_FAILED)
|
||||
return;
|
||||
sem_post(m_pSemaphoreRx);
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline bool CSharedMemBuffer<TAccessor>::WaitForFreeSpace(uint32_t uiTimeoutMs) const
|
||||
{
|
||||
if (!m_pSemaphoreRx || m_pSemaphoreRx == SEM_FAILED)
|
||||
return false;
|
||||
|
||||
// Get the time from the realtime clock
|
||||
timespec sTimespec{};
|
||||
if (clock_gettime(CLOCK_REALTIME, &sTimespec) == -1)
|
||||
return false;
|
||||
uint64_t uiTimeNs = sTimespec.tv_nsec + uiTimeoutMs * 1000000ull;
|
||||
sTimespec.tv_nsec = uiTimeNs % 1000000000ull;
|
||||
sTimespec.tv_sec += uiTimeNs / 1000000000ull;
|
||||
|
||||
// Wait for the semaphore
|
||||
int iResult = sem_timedwait(m_pSemaphoreRx, &sTimespec);
|
||||
if (iResult < 0)
|
||||
return false;
|
||||
if (TAccessor::Canceled())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // !defined POSIX_SHARED_MEM_BUFFER_H
|
||||
509
sdv_services/ipc_shared_mem/shared_mem_buffer_windows.h
Normal file
509
sdv_services/ipc_shared_mem/shared_mem_buffer_windows.h
Normal file
@@ -0,0 +1,509 @@
|
||||
#if !defined WINDOWS_SHARED_MEM_BUFFER_H && defined _WIN32
|
||||
#define WINDOWS_SHARED_MEM_BUFFER_H
|
||||
|
||||
#include <cassert>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
|
||||
#include <support/string.h>
|
||||
#include <support/toml.h>
|
||||
#include "../../global/trace.h"
|
||||
#include "mem_buffer_accessor.h"
|
||||
|
||||
/**
|
||||
* @brief In-process memory buffer.
|
||||
*/
|
||||
template <class TAccessor>
|
||||
class CSharedMemBuffer : public TAccessor
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
* @param[in] uiSize Optional size of the buffer. If zero, a default buffer size of 10k is configured.
|
||||
* @param[in] rssName Optional name to be used for the connection. If empty, a random name is generated.
|
||||
* @param[in] bServer Optional boolean indicating whether the connection is a server (true), which initiates the connection, or
|
||||
* a client (false), which opens an existing connection.
|
||||
*/
|
||||
CSharedMemBuffer(uint32_t uiSize = 0, const std::string& rssName = std::string(), bool bServer = true);
|
||||
|
||||
/**
|
||||
* @brief Connection constructor
|
||||
* @param[in] rssConnectionString Reference to string with connection information.
|
||||
*/
|
||||
CSharedMemBuffer(const std::string& rssConnectionString);
|
||||
|
||||
/** No copy constructor */
|
||||
CSharedMemBuffer(const CSharedMemBuffer&) = delete;
|
||||
|
||||
/** No move constructor */
|
||||
CSharedMemBuffer(CSharedMemBuffer&&) = delete;
|
||||
|
||||
/**
|
||||
* \brief Default destructor
|
||||
*/
|
||||
~CSharedMemBuffer();
|
||||
|
||||
/** No copy-assignment operator */
|
||||
CSharedMemBuffer& operator=(const CSharedMemBuffer&) = delete;
|
||||
|
||||
/** No move-assignment operator */
|
||||
CSharedMemBuffer& operator=(CSharedMemBuffer&&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Detach the buffer. Overload of CMemBufferAccessorBase::Detach.
|
||||
* @details The detach function detaches the shared memory without deleting the memory. This keeps the memory alive for reuse.
|
||||
*/
|
||||
virtual void Detach() override;
|
||||
|
||||
/**
|
||||
* @brief Return the connection string to connect to this shared memory.
|
||||
* @return The connection string to connect to this buffer.
|
||||
*/
|
||||
std::string GetConnectionString() const;
|
||||
|
||||
/**
|
||||
* @brief Trigger listener that a write operation was completed.
|
||||
*/
|
||||
void TriggerDataSend() override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a write operation to be completed.
|
||||
* @param[in] uiTimeoutMs The amount of time (in ms) to wait for a trigger.
|
||||
* @return Returns 'true' when data was stored, 'false' when a timeout occurred.
|
||||
*/
|
||||
bool WaitForData(uint32_t uiTimeoutMs) const override;
|
||||
|
||||
/**
|
||||
* @brief Trigger listener that a read operation was completed.
|
||||
*/
|
||||
void TriggerDataReceive() override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a read operation to be completed.
|
||||
* @param[in] uiTimeoutMs The amount of time (in ms) to wait for a trigger.
|
||||
* @return Returns 'true' when data was stored, 'false' when a timeout occurred.
|
||||
*/
|
||||
bool WaitForFreeSpace(uint32_t uiTimeoutMs) const override;
|
||||
|
||||
/**
|
||||
* @brief Return the last reported error.
|
||||
* @return Reference to the error string.
|
||||
*/
|
||||
const std::string& GetError() const { return m_ssError; }
|
||||
|
||||
/**
|
||||
* @brief Get the size of the buffer.
|
||||
* @return Returns the size of the buffer.
|
||||
*/
|
||||
uint32_t GetSize() const { return m_uiSize; }
|
||||
|
||||
/**
|
||||
* @brief Get the name of the buffer.
|
||||
* @return Reference to the string holding the name of the buffer.
|
||||
*/
|
||||
const std::string& GetName() const { return m_ssName; }
|
||||
|
||||
private:
|
||||
uint32_t m_uiSize = 0u; ///< Size of the shared memory buffer.
|
||||
HANDLE m_hMapFile = INVALID_HANDLE_VALUE; ///< Handle to the shared memory buffer.
|
||||
std::string m_ssName; ///< Name of the shared memory.
|
||||
uint8_t* m_pBuffer = nullptr; ///< Pointer to the mapped buffer.
|
||||
std::string m_ssSyncTx; ///< Name of the signalling event.
|
||||
HANDLE m_hSignalTx = INVALID_HANDLE_VALUE; ///< Handle to the signalling event.
|
||||
std::string m_ssSyncRx; ///< Name of the signalling event.
|
||||
HANDLE m_hSignalRx = INVALID_HANDLE_VALUE; ///< Handle to the signalling event.
|
||||
std::string m_ssError; ///< The last reported error.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Shared memory buffer used for reading.
|
||||
*/
|
||||
using CSharedMemBufferRx = CSharedMemBuffer<CMemBufferAccessorRx>;
|
||||
|
||||
/**
|
||||
* @brief Shared memory buffer used for writing.
|
||||
*/
|
||||
using CSharedMemBufferTx = CSharedMemBuffer<CMemBufferAccessorTx>;
|
||||
|
||||
template <class TAccessor>
|
||||
inline CSharedMemBuffer<TAccessor>::CSharedMemBuffer(uint32_t uiSize /*= 0*/, const std::string& rssName /*= std::string()*/,
|
||||
bool bServer /*= true*/) : m_uiSize(bServer ? (uiSize ? uiSize : 128 * 1024) : 0)
|
||||
{
|
||||
// Create a name to be used in the connection string
|
||||
std::string ssDirectionString;
|
||||
if (bServer)
|
||||
ssDirectionString = TAccessor::GetAccessType() == EAccessType::rx ? "RESPONSE_" : "REQUEST_";
|
||||
else
|
||||
ssDirectionString = TAccessor::GetAccessType() == EAccessType::rx ? "REQUEST_" : "RESPONSE_";
|
||||
if (!rssName.empty())
|
||||
{
|
||||
m_ssName = std::string("SDV_SHARED_") + ssDirectionString + rssName;
|
||||
m_ssSyncTx = std::string("SDV_TX_SYNC_") + ssDirectionString + rssName;
|
||||
m_ssSyncRx = std::string("SDV_RX_SYNC_") + ssDirectionString + rssName;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t uiCnt = std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
||||
m_ssName = std::string("SDV_SHARED_") + ssDirectionString + std::to_string(uiCnt);
|
||||
m_ssSyncTx = std::string("SDV_TX_SYNC_") + ssDirectionString + std::to_string(uiCnt);
|
||||
m_ssSyncRx = std::string("SDV_RX_SYNC_") + ssDirectionString + std::to_string(uiCnt);
|
||||
}
|
||||
|
||||
// Create a path
|
||||
std::string ssNamePath = /*"Global\\" +*/ m_ssName;
|
||||
std::string ssSyncTxPath = /*"Global\\" +*/ m_ssSyncTx;
|
||||
std::string ssSyncRxPath = /*"Global\\" +*/ m_ssSyncRx;
|
||||
|
||||
auto fnReportWin32Error = [this]()
|
||||
{
|
||||
TCHAR* szMsg = nullptr;
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
GetLastError(),
|
||||
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
||||
(LPTSTR) &szMsg,
|
||||
0,
|
||||
NULL);
|
||||
m_ssError += sdv::MakeUtf8String(szMsg);
|
||||
LocalFree(szMsg);
|
||||
};
|
||||
|
||||
auto fnCloseAll = [this]()
|
||||
{
|
||||
if (m_pBuffer) UnmapViewOfFile(m_pBuffer);
|
||||
if (m_hMapFile && m_hMapFile != INVALID_HANDLE_VALUE) CloseHandle(m_hMapFile);
|
||||
if (m_hSignalTx && m_hSignalTx != INVALID_HANDLE_VALUE) CloseHandle(m_hSignalTx);
|
||||
if (m_hSignalRx && m_hSignalRx != INVALID_HANDLE_VALUE) CloseHandle(m_hSignalRx);
|
||||
m_pBuffer = 0;
|
||||
m_hMapFile = INVALID_HANDLE_VALUE;
|
||||
m_hSignalTx = INVALID_HANDLE_VALUE;
|
||||
m_hSignalRx = INVALID_HANDLE_VALUE;
|
||||
};
|
||||
|
||||
// Create sync event object
|
||||
m_hSignalTx = CreateEventA(nullptr, FALSE, FALSE, ssSyncTxPath.c_str());
|
||||
if (!m_hSignalTx || m_hSignalTx == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_ssError = "Failed to create event " + ssSyncTxPath + ": ";
|
||||
fnReportWin32Error();
|
||||
fnCloseAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create sync event object
|
||||
m_hSignalRx = CreateEventA(nullptr, FALSE, FALSE, ssSyncRxPath.c_str());
|
||||
if (!m_hSignalRx || m_hSignalRx == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_ssError = "Failed to create event " + ssSyncRxPath + ": ";
|
||||
fnReportWin32Error();
|
||||
fnCloseAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (bServer)
|
||||
{
|
||||
// Create the file mapping object
|
||||
m_hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, // use paging file
|
||||
NULL, // default security
|
||||
PAGE_READWRITE, // read/write access
|
||||
0, // maximum object size (high-order DWORD)
|
||||
m_uiSize, // maximum object size (low-order DWORD)
|
||||
ssNamePath.c_str()); // name of mapping object
|
||||
} else
|
||||
{
|
||||
// Open the file mapping object
|
||||
m_hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, // read/write access
|
||||
FALSE, // do not inherit the name
|
||||
ssNamePath.c_str()); // name of mapping object
|
||||
|
||||
}
|
||||
if (!m_hMapFile || m_hMapFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_ssError = "Failed to access file mapping " + ssNamePath + ": ";
|
||||
fnReportWin32Error();
|
||||
fnCloseAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Map the file into memory
|
||||
m_pBuffer = reinterpret_cast<uint8_t*>(MapViewOfFile(m_hMapFile, // handle to map object
|
||||
FILE_MAP_ALL_ACCESS, // read/write permission
|
||||
0, // Offset high
|
||||
0, // Offset low
|
||||
m_uiSize)); // Amount of bytes
|
||||
if (!m_pBuffer)
|
||||
{
|
||||
m_ssError = "Failed to create file mapping view: ";
|
||||
fnReportWin32Error();
|
||||
fnCloseAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_uiSize)
|
||||
{
|
||||
// Request the size of the mapping
|
||||
MEMORY_BASIC_INFORMATION sMemInfo{};
|
||||
auto nRet = VirtualQuery(m_pBuffer, &sMemInfo, sizeof(sMemInfo));
|
||||
if (nRet != sizeof(sMemInfo) || !sMemInfo.RegionSize)
|
||||
{
|
||||
m_ssError = "Failed to request mapping view size: ";
|
||||
fnReportWin32Error();
|
||||
fnCloseAll();
|
||||
return;
|
||||
}
|
||||
m_uiSize = static_cast<uint32_t>(sMemInfo.RegionSize);
|
||||
}
|
||||
|
||||
// If this is a server, the size causes the initialization. For a client, no initialization should take place (the server has
|
||||
// done so already).
|
||||
TAccessor::Attach(m_pBuffer, bServer ? m_uiSize : 0);
|
||||
|
||||
TRACE("Accessed shared memory for ", m_ssSyncTx, " and ", m_ssSyncRx, ".");
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline CSharedMemBuffer<TAccessor>::CSharedMemBuffer(const std::string& rssConnectionString)
|
||||
{
|
||||
if (rssConnectionString.empty())
|
||||
{
|
||||
m_ssError = "Missing connection string.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Interpret the connection string
|
||||
sdv::toml::CTOMLParser config(rssConnectionString);
|
||||
|
||||
// The connection string can contain multiple parameters. Search for the first parameters fitting the accessor direction
|
||||
size_t nIndex = 0;
|
||||
sdv::toml::CNodeCollection nodeConnectParamCollection = config.GetDirect("ConnectParam");
|
||||
do
|
||||
{
|
||||
sdv::toml::CNodeCollection nodeConnectParam;
|
||||
switch (nodeConnectParamCollection.GetType())
|
||||
{
|
||||
case sdv::toml::ENodeType::node_array:
|
||||
if (nIndex >= nodeConnectParamCollection.GetCount()) break;
|
||||
nodeConnectParam = nodeConnectParamCollection[nIndex];
|
||||
break;
|
||||
case sdv::toml::ENodeType::node_table:
|
||||
if (nIndex > 0) break;
|
||||
nodeConnectParam = nodeConnectParamCollection;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (nodeConnectParam.GetType() != sdv::toml::ENodeType::node_table) break;
|
||||
|
||||
nIndex++;
|
||||
|
||||
// Check for shared memory
|
||||
if (nodeConnectParam.GetDirect("Type").GetValue() != "shared_mem") continue;
|
||||
|
||||
// Check the direction
|
||||
if (nodeConnectParam.GetDirect("Direction").GetValue() !=
|
||||
(TAccessor::GetAccessType() == EAccessType::rx ? "response" : "request"))
|
||||
continue;
|
||||
|
||||
// Get the information
|
||||
m_ssName = static_cast<std::string>(nodeConnectParam.GetDirect("Location").GetValue());
|
||||
m_ssSyncTx = static_cast<std::string>(nodeConnectParam.GetDirect("SyncTx").GetValue());
|
||||
m_ssSyncRx = static_cast<std::string>(nodeConnectParam.GetDirect("SyncRx").GetValue());
|
||||
break;
|
||||
} while (true);
|
||||
if (m_ssName.empty() || m_ssSyncTx.empty() || m_ssSyncRx.empty())
|
||||
{
|
||||
m_ssError = "Incomplete connection information.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a path
|
||||
std::string ssPath = /*"Global\\" +*/ m_ssName;
|
||||
std::string ssSyncTxPath = /*"Global\\" +*/ m_ssSyncTx;
|
||||
std::string ssSyncRxPath = /*"Global\\" +*/ m_ssSyncRx;
|
||||
|
||||
auto fnReportWin32Error = [this]()
|
||||
{
|
||||
TCHAR* szMsg = nullptr;
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
GetLastError(),
|
||||
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
|
||||
(LPTSTR)&szMsg,
|
||||
0,
|
||||
NULL);
|
||||
m_ssError += sdv::MakeUtf8String(szMsg);
|
||||
LocalFree(szMsg);
|
||||
};
|
||||
|
||||
auto fnCloseAll = [this]()
|
||||
{
|
||||
if (m_pBuffer) UnmapViewOfFile(m_pBuffer);
|
||||
if (m_hMapFile && m_hMapFile != INVALID_HANDLE_VALUE) CloseHandle(m_hMapFile);
|
||||
if (m_hSignalTx && m_hSignalTx != INVALID_HANDLE_VALUE) CloseHandle(m_hSignalTx);
|
||||
if (m_hSignalRx && m_hSignalRx != INVALID_HANDLE_VALUE) CloseHandle(m_hSignalRx);
|
||||
m_pBuffer = 0;
|
||||
m_hMapFile = INVALID_HANDLE_VALUE;
|
||||
m_hSignalTx = INVALID_HANDLE_VALUE;
|
||||
m_hSignalRx = INVALID_HANDLE_VALUE;
|
||||
};
|
||||
|
||||
// Create TX sync event object
|
||||
m_hSignalTx = CreateEventA(nullptr, FALSE, FALSE, ssSyncTxPath.c_str());
|
||||
if (!m_hSignalTx || m_hSignalTx == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_ssError = "Failed to create event " + ssSyncTxPath + ": ";
|
||||
fnReportWin32Error();
|
||||
fnCloseAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create RX sync event object
|
||||
m_hSignalRx = CreateEventA(nullptr, FALSE, FALSE, ssSyncRxPath.c_str());
|
||||
if (!m_hSignalRx || m_hSignalRx == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_ssError = "Failed to create event " + ssSyncRxPath + ": ";
|
||||
fnReportWin32Error();
|
||||
fnCloseAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the file mapping object
|
||||
m_hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, // read/write access
|
||||
FALSE, // do not inherit the name
|
||||
ssPath.c_str()); // name of mapping object
|
||||
if (!m_hMapFile || m_hMapFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_ssError = "Failed to open file mapping " + ssPath + ": ";
|
||||
fnReportWin32Error();
|
||||
fnCloseAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Map the file into memory
|
||||
m_pBuffer = reinterpret_cast<uint8_t*>(MapViewOfFile(m_hMapFile, // handle to map object
|
||||
FILE_MAP_ALL_ACCESS, // read/write permission
|
||||
0, // Offset high
|
||||
0, // Offset low
|
||||
0)); // Amount of bytes (all in this case)
|
||||
if (!m_pBuffer)
|
||||
{
|
||||
m_ssError = "Failed to create file mapping view: ";
|
||||
fnReportWin32Error();
|
||||
fnCloseAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Request the size of the mapping
|
||||
MEMORY_BASIC_INFORMATION sMemInfo{};
|
||||
auto nRet = VirtualQuery(m_pBuffer, &sMemInfo, sizeof(sMemInfo));
|
||||
if (nRet != sizeof(sMemInfo) || !sMemInfo.RegionSize)
|
||||
{
|
||||
m_ssError = "Failed to request mapping view size: ";
|
||||
fnReportWin32Error();
|
||||
fnCloseAll();
|
||||
return;
|
||||
}
|
||||
m_uiSize = static_cast<uint32_t>(sMemInfo.RegionSize);
|
||||
|
||||
TAccessor::Attach(m_pBuffer);
|
||||
|
||||
TRACE("Opened shared memory for ", m_ssSyncTx, " and ", m_ssSyncRx, ".");
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
CSharedMemBuffer<TAccessor>::~CSharedMemBuffer()
|
||||
{
|
||||
if (m_pBuffer) UnmapViewOfFile(m_pBuffer);
|
||||
if (m_hMapFile && m_hMapFile != INVALID_HANDLE_VALUE) CloseHandle(m_hMapFile);
|
||||
if (m_hSignalTx && m_hSignalTx != INVALID_HANDLE_VALUE) CloseHandle(m_hSignalTx);
|
||||
if (m_hSignalRx && m_hSignalRx != INVALID_HANDLE_VALUE) CloseHandle(m_hSignalRx);
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
void CSharedMemBuffer<TAccessor>::Detach()
|
||||
{
|
||||
if (m_pBuffer) UnmapViewOfFile(m_pBuffer);
|
||||
if (m_hMapFile && m_hMapFile != INVALID_HANDLE_VALUE) CloseHandle(m_hMapFile);
|
||||
if (m_hSignalTx && m_hSignalTx != INVALID_HANDLE_VALUE) CloseHandle(m_hSignalTx);
|
||||
if (m_hSignalRx && m_hSignalRx != INVALID_HANDLE_VALUE) CloseHandle(m_hSignalRx);
|
||||
m_uiSize = 0u;
|
||||
m_hMapFile = INVALID_HANDLE_VALUE;
|
||||
m_ssName.clear();
|
||||
m_pBuffer = nullptr;
|
||||
m_ssSyncTx.clear();
|
||||
m_hSignalTx = INVALID_HANDLE_VALUE;
|
||||
m_ssSyncRx.clear();
|
||||
m_hSignalRx = INVALID_HANDLE_VALUE;
|
||||
m_ssError.clear();
|
||||
}
|
||||
|
||||
|
||||
template <class TAccessor>
|
||||
inline std::string CSharedMemBuffer<TAccessor>::GetConnectionString() const
|
||||
{
|
||||
// The connection string contains the TOML file for connecting to this shared memory.
|
||||
std::stringstream sstream;
|
||||
sstream << "[[ConnectParam]]" << std::endl;
|
||||
sstream << "Type = \"shared_mem\"" << std::endl;
|
||||
sstream << "Location = \"" << m_ssName << "\"" << std::endl;
|
||||
sstream << "SyncTx = \"" << m_ssSyncTx << "\"" << std::endl;
|
||||
sstream << "SyncRx = \"" << m_ssSyncRx << "\"" << std::endl;
|
||||
// The target direction is the opposite of the direction of the accessor. Therefore, if the accessor uses an RX access type,
|
||||
// the target uses an TX access type and should be configured as response, otherwise it is a request.
|
||||
sstream << "Direction = \"" << (TAccessor::GetAccessType() == EAccessType::rx ? "request" : "response") << "\"" << std::endl;
|
||||
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline void CSharedMemBuffer<TAccessor>::TriggerDataSend()
|
||||
{
|
||||
if (!m_hSignalTx || m_hSignalTx == INVALID_HANDLE_VALUE) return;
|
||||
SetEvent(m_hSignalTx);
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline bool CSharedMemBuffer<TAccessor>::WaitForData(uint32_t uiTimeoutMs) const
|
||||
{
|
||||
if (!m_hSignalTx || m_hSignalTx == INVALID_HANDLE_VALUE) return false;
|
||||
|
||||
// Check whether there is data; if so, return true.
|
||||
if (TAccessor::HasUnreadData())
|
||||
return true;
|
||||
|
||||
return WaitForSingleObject(m_hSignalTx, uiTimeoutMs) == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline void CSharedMemBuffer<TAccessor>::TriggerDataReceive()
|
||||
{
|
||||
if (!m_hSignalRx || m_hSignalRx == INVALID_HANDLE_VALUE) return;
|
||||
SetEvent(m_hSignalRx);
|
||||
}
|
||||
|
||||
template <class TAccessor>
|
||||
inline bool CSharedMemBuffer<TAccessor>::WaitForFreeSpace(uint32_t uiTimeoutMs) const
|
||||
{
|
||||
if (!m_hSignalRx || m_hSignalRx == INVALID_HANDLE_VALUE) return false;
|
||||
|
||||
DWORD dwResult = WaitForSingleObject(m_hSignalRx, uiTimeoutMs);
|
||||
if (TAccessor::Canceled())
|
||||
return false;
|
||||
return dwResult == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
#endif // !defined WINDOWS_SHARED_MEM_BUFFER_H
|
||||
192
sdv_services/ipc_shared_mem/watchdog.cpp
Normal file
192
sdv_services/ipc_shared_mem/watchdog.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
#include "watchdog.h"
|
||||
#include "connection.h"
|
||||
#include <vector>
|
||||
#include "../../global/trace.h"
|
||||
|
||||
#ifdef __unix__
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
CWatchDog::CWatchDog()
|
||||
{
|
||||
m_threadScheduledConnectionDestructions = std::thread(&CWatchDog::HandleScheduledConnectionDestructions, this);
|
||||
}
|
||||
|
||||
CWatchDog::~CWatchDog()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
void CWatchDog::Clear()
|
||||
{
|
||||
sdv::process::IProcessLifetime* pMonitorMgnt = sdv::core::GetObject<sdv::process::IProcessLifetime>("ProcessControlService");
|
||||
if (pMonitorMgnt)
|
||||
{
|
||||
std::unique_lock<std::mutex> lockMonitors(m_mtxMonitors);
|
||||
for (const auto& rvtProcessMonitor : m_mapProcessMonitors)
|
||||
pMonitorMgnt->UnregisterMonitor(rvtProcessMonitor.second);
|
||||
m_mapProcessMonitors.clear();
|
||||
}
|
||||
|
||||
|
||||
// Shift the connections to a local variable to be able to delete the connections without being in the lock region.
|
||||
std::unique_lock<std::mutex> lockConnections(m_mtxConnections);
|
||||
auto mapConnections = std::move(m_mapConnections);
|
||||
lockConnections.unlock();
|
||||
|
||||
// Remove the connections.
|
||||
mapConnections.clear();
|
||||
|
||||
// Finalize the asnyhronous destructions
|
||||
m_bShutdown = true;
|
||||
if (m_threadScheduledConnectionDestructions.joinable())
|
||||
m_threadScheduledConnectionDestructions.join();
|
||||
}
|
||||
|
||||
void CWatchDog::AddConnection(const std::shared_ptr<CConnection>& rptrConnection)
|
||||
{
|
||||
if (!rptrConnection) return;
|
||||
|
||||
#if ENABLE_REPORTING > 0
|
||||
TRACE("Registering connection ", rptrConnection->IsServer() ? "server" : "client");
|
||||
#endif
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxConnections);
|
||||
m_mapConnections.try_emplace(rptrConnection.get(), rptrConnection);
|
||||
}
|
||||
|
||||
void CWatchDog::RemoveConnection(CConnection* pConnection, bool bAsync)
|
||||
{
|
||||
#if ENABLE_REPORTING > 0
|
||||
if (bAsync)
|
||||
TRACE("Scheduling destruction connection ", pConnection->IsServer() ? "server" : "client");
|
||||
else
|
||||
TRACE("Destroying connection ", pConnection->IsServer() ? "server" : "client");
|
||||
#endif
|
||||
|
||||
// Find the connection and move it in a local variable to release it when outside the lock region.
|
||||
std::unique_lock<std::mutex> lockConnections(m_mtxConnections);
|
||||
auto itConnection = m_mapConnections.find(pConnection);
|
||||
if (itConnection == m_mapConnections.end()) return;
|
||||
auto ptrConnection = std::move(itConnection->second);
|
||||
m_mapConnections.erase(itConnection);
|
||||
if (bAsync)
|
||||
{
|
||||
// Shift the connection into the queue.
|
||||
m_queueScheduledConnectionDestructions.push(std::move(ptrConnection));
|
||||
m_cvTriggerConnectionDestruction.notify_all();
|
||||
}
|
||||
lockConnections.unlock();
|
||||
|
||||
// Release the connection
|
||||
ptrConnection.reset();
|
||||
}
|
||||
|
||||
void CWatchDog::AddMonitor(sdv::process::TProcessID tProcessID, CConnection* pConnection)
|
||||
{
|
||||
if (!tProcessID || !pConnection) return;
|
||||
|
||||
sdv::process::IProcessLifetime* pMonitorMgnt = sdv::core::GetObject<sdv::process::IProcessLifetime>("ProcessControlService");
|
||||
if (!pMonitorMgnt) return;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxMonitors);
|
||||
|
||||
#if ENABLE_REPORTING > 0
|
||||
TRACE("Registering ", pConnection->IsServer() ? "server" : "client", " watchdog monitor for PID#", tProcessID);
|
||||
#endif
|
||||
|
||||
// Add the process monitor if not already assigned
|
||||
m_mapProcessMonitors.try_emplace(tProcessID, pMonitorMgnt->RegisterMonitor(tProcessID, this));
|
||||
|
||||
#if ENABLE_REPORTING > 0
|
||||
TRACE("Registering watchdog for PID#", tProcessID);
|
||||
#endif
|
||||
|
||||
// Add a monitor entry
|
||||
m_mapMonitors.insert(std::make_pair(tProcessID, pConnection->shared_from_this()));
|
||||
}
|
||||
|
||||
void CWatchDog::RemoveMonitor(const CConnection* pConnection)
|
||||
{
|
||||
if (!pConnection) return;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxMonitors);
|
||||
|
||||
#if ENABLE_REPORTING > 0
|
||||
TRACE("Removing ", pConnection->IsServer() ? "server" : "client", " watchdog monitor");
|
||||
#endif
|
||||
|
||||
// Earse all monitors for the provided connection
|
||||
auto itMonitor = m_mapMonitors.begin();
|
||||
while (itMonitor != m_mapMonitors.end())
|
||||
{
|
||||
std::shared_ptr<CConnection> ptrConnection = itMonitor->second.lock();
|
||||
if (ptrConnection.get() == pConnection)
|
||||
itMonitor = m_mapMonitors.erase(itMonitor);
|
||||
else
|
||||
itMonitor++;
|
||||
}
|
||||
}
|
||||
|
||||
void CWatchDog::HandleScheduledConnectionDestructions()
|
||||
{
|
||||
while (!m_bShutdown)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxMonitors);
|
||||
m_cvTriggerConnectionDestruction.wait_for(lock, std::chrono::milliseconds(100));
|
||||
while (m_queueScheduledConnectionDestructions.size())
|
||||
{
|
||||
std::shared_ptr<CConnection> ptrConnection = std::move(m_queueScheduledConnectionDestructions.front());
|
||||
m_queueScheduledConnectionDestructions.pop();
|
||||
lock.unlock();
|
||||
|
||||
#if ENABLE_REPORTING > 0
|
||||
TRACE("Final destroying connection ", ptrConnection->IsServer() ? "server" : "client");
|
||||
#endif
|
||||
|
||||
ptrConnection.reset();
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CWatchDog::ProcessTerminated(/*in*/ sdv::process::TProcessID tProcessID, /*in*/ int64_t /*iRetValue*/)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mtxMonitors);
|
||||
|
||||
// Unregister the monitor
|
||||
sdv::process::IProcessLifetime* pMonitorMgnt = sdv::core::GetObject<sdv::process::IProcessLifetime>("ProcessControlService");
|
||||
if (!pMonitorMgnt) return;
|
||||
auto itProcessMonitor = m_mapProcessMonitors.find(tProcessID);
|
||||
if (itProcessMonitor == m_mapProcessMonitors.end()) return;
|
||||
pMonitorMgnt->UnregisterMonitor(itProcessMonitor->second);
|
||||
m_mapProcessMonitors.erase(itProcessMonitor);
|
||||
|
||||
// Find the monitor in the map, remove it and add the connection to a to-be-disconnected vector.
|
||||
std::vector<std::shared_ptr<CConnection>> vecDisconnectedConnections;
|
||||
auto itMonitor = m_mapMonitors.find(tProcessID);
|
||||
while (itMonitor != m_mapMonitors.end() && itMonitor->first == tProcessID)
|
||||
{
|
||||
#if ENABLE_REPORTING > 0
|
||||
TRACE("Removing watchdog for PID#", tProcessID);
|
||||
#endif
|
||||
// Add the connection to the vector (if the connection was not removed before).
|
||||
auto ptrConnection = itMonitor->second.lock();
|
||||
if (ptrConnection) vecDisconnectedConnections.push_back(ptrConnection);
|
||||
|
||||
// Remove the monitor
|
||||
itMonitor = m_mapMonitors.erase(itMonitor);
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
|
||||
// Inform the connection about the removed process.
|
||||
for (auto& rptrConnection : vecDisconnectedConnections)
|
||||
{
|
||||
rptrConnection->SetStatus(sdv::ipc::EConnectStatus::disconnected_forced);
|
||||
|
||||
#if ENABLE_REPORTING > 0
|
||||
TRACE("Forced disconnection for PID#", tProcessID);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
120
sdv_services/ipc_shared_mem/watchdog.h
Normal file
120
sdv_services/ipc_shared_mem/watchdog.h
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef WATCH_DOG_H
|
||||
#define WATCH_DOG_H
|
||||
|
||||
#ifdef _WIN32
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <interfaces/process.h>
|
||||
#include <support/interface_ptr.h>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
#include <condition_variable>
|
||||
|
||||
// Forward declaration
|
||||
class CConnection;
|
||||
|
||||
/**
|
||||
* @brief The watch dog monitors the processes and connections.
|
||||
* @details The watch dog keeps track of the processes and disconnects when the process the connection is with doesn't exist any
|
||||
* more. This method is has advantages to using time-outs. If a process is being debugged and a breakpoint is triggered, all
|
||||
* threads within the process are paused, which could cause a timeout by the calling process waiting for an answer. By monitoring
|
||||
* the process existence, a pause doesn't cause any error. If the process crashes, it is being removed by the OS, causing the
|
||||
* monitor to detect this and the connection being terminated. Disadvantage is, that if the process is in a deadlock, this cannot
|
||||
* be detected by this method.
|
||||
*/
|
||||
class CWatchDog : public sdv::IInterfaceAccess, public sdv::process::IProcessLifetimeCallback
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CWatchDog();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CWatchDog();
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::process::IProcessLifetimeCallback)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Unregister all remaining monitors.
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* @brief Add a connection object used for monitoring.
|
||||
* @param[in] rptrConnection Reference to the connection shared pointer.
|
||||
*/
|
||||
void AddConnection(const std::shared_ptr<CConnection>& rptrConnection);
|
||||
|
||||
/**
|
||||
* @brief Remove a connection.
|
||||
* @param[in] pConnection The connection pointer to remove the monitor for.
|
||||
* @param[in] bAsync Use asnyhronous removal (use when the removal was triggered by a thread owned by the connection).
|
||||
*/
|
||||
void RemoveConnection(CConnection* pConnection, bool bAsync);
|
||||
|
||||
/**
|
||||
* @brief Add a process monitor with the connection object to information.
|
||||
* @param[in] tProcessID Process ID of the process to monitor.
|
||||
* @param[in] pConnection Pointer to the connection to inform when the process is killed.
|
||||
*/
|
||||
void AddMonitor(sdv::process::TProcessID tProcessID, CConnection* pConnection);
|
||||
|
||||
/**
|
||||
* @brief Remove a monitor.
|
||||
* @param[in] pConnection The connection pointer to remove the monitor for.
|
||||
*/
|
||||
void RemoveMonitor(const CConnection* pConnection);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Thread function to destroy scheduled connections for destruction.
|
||||
*/
|
||||
void HandleScheduledConnectionDestructions();
|
||||
|
||||
/**
|
||||
* @brief Called when the process was terminated. Overload of sdv::process::IProcessLifetimeCallback::ProcessTerminated.
|
||||
* @remarks The process return value is not always valid. The validity depends on the support of the underlying system.
|
||||
* @param[in] tProcessID The process ID of the process being terminated.
|
||||
* @param[in] iRetValue Process return value or 0 when not supported.
|
||||
*/
|
||||
virtual void ProcessTerminated(/*in*/ sdv::process::TProcessID tProcessID, /*in*/ int64_t iRetValue) override;
|
||||
|
||||
std::mutex m_mtxConnections; ///< Protect the map access.
|
||||
std::map<CConnection*, std::shared_ptr<CConnection>> m_mapConnections; ///< Connection map.
|
||||
|
||||
std::mutex m_mtxMonitors; ///< Protect the map access.
|
||||
std::multimap<sdv::process::TProcessID, std::weak_ptr<CConnection>> m_mapMonitors; ///< Process monitor.
|
||||
std::map<sdv::process::TProcessID, uint32_t> m_mapProcessMonitors; ///< Map of process monitor cookies.
|
||||
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.
|
||||
bool m_bShutdown = false; ///< Set when shutting down the watchdog
|
||||
};
|
||||
|
||||
#endif // !defined WATCH_DOG_H
|
||||
24
sdv_services/ipc_sockets/CMakeLists.txt
Normal file
24
sdv_services/ipc_sockets/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
if(WIN32)
|
||||
# Define project
|
||||
project(ipc_sockets VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(ipc_sockets SHARED
|
||||
"channel_mgnt.h"
|
||||
"channel_mgnt.cpp"
|
||||
"connection.h"
|
||||
"connection.cpp")
|
||||
target_link_libraries(ipc_sockets ${CMAKE_THREAD_LIBS_INIT} Ws2_32.lib)
|
||||
target_link_options(ipc_sockets PRIVATE)
|
||||
target_include_directories(ipc_sockets PRIVATE ./include/)
|
||||
|
||||
set_target_properties(ipc_sockets PROPERTIES PREFIX "")
|
||||
set_target_properties(ipc_sockets PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(ipc_sockets CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} ipc_sockets PARENT_SCOPE)
|
||||
|
||||
endif()
|
||||
356
sdv_services/ipc_sockets/channel_mgnt.cpp
Normal file
356
sdv_services/ipc_sockets/channel_mgnt.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
#include "channel_mgnt.h"
|
||||
#include "connection.h"
|
||||
#include "../../global/base64.h"
|
||||
#include <support/toml.h>
|
||||
#include <interfaces/process.h>
|
||||
#include <future>
|
||||
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
#pragma push_macro("GetObject")
|
||||
#undef GetObject
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <array>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("GetObject")
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Define for the connection string
|
||||
*/
|
||||
#define SHARED_SOCKET "SHARED_SOCKET"
|
||||
|
||||
void CSocketsChannelMgnt::Initialize(const sdv::u8string& /*ssObjectConfig*/)
|
||||
{
|
||||
if (m_eObjectStatus != sdv::EObjectStatus::initialization_pending)
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
|
||||
else
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CSocketsChannelMgnt::GetStatus() const
|
||||
{
|
||||
return m_eObjectStatus;
|
||||
}
|
||||
|
||||
void CSocketsChannelMgnt::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::running || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::configuring;
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::configuring || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::running;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CSocketsChannelMgnt::Shutdown()
|
||||
{
|
||||
//m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
//...
|
||||
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
|
||||
}
|
||||
|
||||
sdv::ipc::SChannelEndpoint CSocketsChannelMgnt::CreateEndpoint(const sdv::u8string& /*ssChannelConfig*/)
|
||||
{
|
||||
StartUpWinSock();
|
||||
addrinfo hints;
|
||||
ZeroMemory(&hints, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
SOCKET listenSocket = CreateSocket(hints);
|
||||
|
||||
if (listenSocket == INVALID_SOCKET)
|
||||
{
|
||||
SDV_LOG_ERROR("CreateSocket failed (could not create endpoint)");
|
||||
sdv::ipc::SChannelEndpoint connectionEndpoint{};
|
||||
return connectionEndpoint;
|
||||
}
|
||||
|
||||
CConnection* pRemoteIPCConnection = new CConnection(listenSocket, true);
|
||||
|
||||
uint16_t port = GetPort(listenSocket);
|
||||
std::string ipcCompleteConfig = "localhost";
|
||||
ipcCompleteConfig.append(";");
|
||||
ipcCompleteConfig.append(std::to_string(port));
|
||||
SDV_LOG_INFO("IPC command param: '", ipcCompleteConfig, "'");
|
||||
|
||||
sdv::ipc::SChannelEndpoint connectionEndpoint{};
|
||||
connectionEndpoint.pConnection = static_cast<IInterfaceAccess*>(pRemoteIPCConnection);
|
||||
connectionEndpoint.ssConnectString = ipcCompleteConfig;
|
||||
|
||||
return connectionEndpoint;
|
||||
}
|
||||
|
||||
|
||||
sdv::IInterfaceAccess* CSocketsChannelMgnt::Access(const sdv::u8string& ssConnectString)
|
||||
{
|
||||
bool sharedSocketRequired = ssConnectString.find(SHARED_SOCKET) != std::string::npos ? true : false;
|
||||
|
||||
if (sharedSocketRequired)
|
||||
{
|
||||
std::string base64Data(ssConnectString);
|
||||
const std::string ext(SHARED_SOCKET);
|
||||
base64Data = base64Data.substr(0, base64Data.size() - ext.size());
|
||||
|
||||
WSAPROTOCOL_INFO socketInfo = DecodeBase64<WSAPROTOCOL_INFO>(base64Data);
|
||||
|
||||
SOCKET sharedSocket = WSASocket(0, 0, 0, &socketInfo, 0, 0);
|
||||
std::string success = "Socket sharing success";
|
||||
if (sharedSocket == INVALID_SOCKET)
|
||||
{
|
||||
success = "Socket sharing did not work!";
|
||||
}
|
||||
|
||||
return static_cast<IInterfaceAccess*>(new CConnection(sharedSocket, false));
|
||||
}
|
||||
|
||||
StartUpWinSock();
|
||||
addrinfo hints;
|
||||
ZeroMemory(&hints, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
std::string host{"localhost"};
|
||||
std::string param{ssConnectString};
|
||||
auto it = param.find(";");
|
||||
if (it != std::string::npos)
|
||||
{
|
||||
host = param.substr(0, it);
|
||||
param = param.substr(it + 1, param.size() - it - 1);
|
||||
}
|
||||
|
||||
SOCKET socket = CreateAndConnectToExistingSocket(hints, host.c_str(), param.c_str());
|
||||
if (socket == INVALID_SOCKET)
|
||||
{
|
||||
SDV_LOG_ERROR("Could not create my socket and connect to the existing socket.");
|
||||
}
|
||||
|
||||
return static_cast<IInterfaceAccess*>(new CConnection(socket, false));
|
||||
}
|
||||
|
||||
uint16_t CSocketsChannelMgnt::GetPort(SOCKET socket) const
|
||||
{
|
||||
sockaddr_in sockAddr;
|
||||
sockAddr.sin_port = 0;
|
||||
int nameLength = sizeof(sockAddr);
|
||||
getsockname(socket, reinterpret_cast<sockaddr*>(&sockAddr), &nameLength);
|
||||
return ntohs(sockAddr.sin_port);
|
||||
}
|
||||
|
||||
SOCKET CSocketsChannelMgnt::CreateAndConnectToExistingSocket(const addrinfo& hints,
|
||||
const char* hostName,
|
||||
const char* portName)
|
||||
{
|
||||
SOCKET invalidSocket{INVALID_SOCKET};
|
||||
|
||||
// Resolve the server address and port
|
||||
CAddrInfo result;
|
||||
int error = getaddrinfo(hostName, portName, &hints, &result.AddressInfo);
|
||||
if (error != 0)
|
||||
{
|
||||
|
||||
SDV_LOG_ERROR("getaddrinfo failed with error: ",
|
||||
std::to_string(error),
|
||||
" host: ",
|
||||
hostName,
|
||||
" port: ",
|
||||
portName);
|
||||
return invalidSocket;
|
||||
}
|
||||
|
||||
SOCKET connectSocket{INVALID_SOCKET};
|
||||
// Attempt to connect to an address until one succeeds
|
||||
for (addrinfo* ptr = result.AddressInfo; ptr != NULL; ptr = ptr->ai_next)
|
||||
{
|
||||
// Create a SOCKET for connecting to server
|
||||
connectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
|
||||
if (connectSocket == INVALID_SOCKET)
|
||||
{
|
||||
SDV_LOG_ERROR("socket failed with error: ", std::to_string(error));
|
||||
return invalidSocket;
|
||||
}
|
||||
|
||||
// Connect to server.
|
||||
error = connect(connectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
|
||||
if (error == SOCKET_ERROR)
|
||||
{
|
||||
SDV_LOG_ERROR("connect failed with error: ", std::to_string(error));
|
||||
closesocket(connectSocket);
|
||||
connectSocket = INVALID_SOCKET;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return connectSocket;
|
||||
}
|
||||
|
||||
SOCKET CSocketsChannelMgnt::CreateSocket(const addrinfo& hints)
|
||||
{
|
||||
static constexpr const char* defaultPort_0{"0"}; // In case a defined port is required
|
||||
SOCKET invalidSocket{INVALID_SOCKET};
|
||||
CAddrInfo result;
|
||||
int error = getaddrinfo(NULL, defaultPort_0, &hints, &result.AddressInfo);
|
||||
if (error != 0)
|
||||
{
|
||||
SDV_LOG_ERROR("getaddrinfo failed with error: ", std::to_string(error));
|
||||
return invalidSocket;
|
||||
}
|
||||
|
||||
SOCKET connectSocket{INVALID_SOCKET};
|
||||
connectSocket =
|
||||
socket(result.AddressInfo->ai_family, result.AddressInfo->ai_socktype, result.AddressInfo->ai_protocol);
|
||||
if (connectSocket == INVALID_SOCKET)
|
||||
{
|
||||
SDV_LOG_ERROR("error at socket(): ", std::to_string(error));
|
||||
return invalidSocket;
|
||||
}
|
||||
|
||||
error = bind(connectSocket, result.AddressInfo->ai_addr, (int)result.AddressInfo->ai_addrlen);
|
||||
if (error == SOCKET_ERROR)
|
||||
{
|
||||
closesocket(connectSocket);
|
||||
SDV_LOG_ERROR("bind failed with error: ", std::to_string(error));
|
||||
return invalidSocket;
|
||||
}
|
||||
|
||||
if (listen(connectSocket, SOMAXCONN) == SOCKET_ERROR)
|
||||
{
|
||||
closesocket(connectSocket);
|
||||
SDV_LOG_ERROR("listen failed with error: ", std::to_string(WSAGetLastError()));
|
||||
return invalidSocket;
|
||||
}
|
||||
|
||||
// Change the socket mode on the listening socket from blocking to
|
||||
// non-block so the application will not block waiting for requests
|
||||
u_long NonBlock = 1;
|
||||
if (ioctlsocket(connectSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)
|
||||
{
|
||||
closesocket(connectSocket);
|
||||
SDV_LOG_ERROR("ioctlsocket failed with error: ", std::to_string(WSAGetLastError()));
|
||||
return invalidSocket;
|
||||
}
|
||||
|
||||
return connectSocket;
|
||||
}
|
||||
|
||||
|
||||
SOCKET CSocketsChannelMgnt::CreateAndConnectToSocket(const addrinfo& hints, const char* defaultHost, const char* defaultPort)
|
||||
{
|
||||
SOCKET ConnectSocket{ INVALID_SOCKET };
|
||||
|
||||
// Resolve the server address and port
|
||||
CAddrInfo result;
|
||||
if (getaddrinfo(defaultHost, defaultPort, &hints, &result.AddressInfo) != 0)
|
||||
{
|
||||
SDV_LOG_ERROR("getaddrinfo() failed: ", std::to_string(WSAGetLastError()));
|
||||
return ConnectSocket;
|
||||
}
|
||||
|
||||
// Attempt to connect to an address until one succeeds
|
||||
for (addrinfo* ptr = result.AddressInfo; ptr != NULL; ptr = ptr->ai_next)
|
||||
{
|
||||
// Create a SOCKET for connecting to server
|
||||
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
|
||||
if (ConnectSocket == INVALID_SOCKET)
|
||||
{
|
||||
SDV_LOG_ERROR("creating SOCKET for connecting failed: ", std::to_string(WSAGetLastError()));
|
||||
break;
|
||||
}
|
||||
|
||||
// Connect to server.
|
||||
if (connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen) == SOCKET_ERROR)
|
||||
{
|
||||
SDV_LOG_ERROR("connect to servcer failed: ", std::to_string(WSAGetLastError()));
|
||||
closesocket(ConnectSocket);
|
||||
ConnectSocket = INVALID_SOCKET;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (ConnectSocket == INVALID_SOCKET)
|
||||
{
|
||||
SDV_LOG_ERROR("Failed to create valid sockaet in CreateAndConnectToSocket()");
|
||||
}
|
||||
|
||||
return ConnectSocket;
|
||||
}
|
||||
|
||||
SOCKET CSocketsChannelMgnt::Listen(const addrinfo& hints, uint32_t port)
|
||||
{
|
||||
SOCKET listenSocket = INVALID_SOCKET;
|
||||
CAddrInfo result;
|
||||
int error = getaddrinfo(NULL, std::to_string(port).c_str(), &hints, &result.AddressInfo);
|
||||
if (error != 0)
|
||||
{
|
||||
SDV_LOG_ERROR("getaddrinfo() failed: ", std::to_string(WSAGetLastError()));
|
||||
return listenSocket;
|
||||
}
|
||||
|
||||
listenSocket = socket(result.AddressInfo->ai_family, result.AddressInfo->ai_socktype, result.AddressInfo->ai_protocol);
|
||||
if (listenSocket == INVALID_SOCKET)
|
||||
{
|
||||
SDV_LOG_ERROR("2creating SOCKET for connecting failed: failed: ", std::to_string(WSAGetLastError()));
|
||||
return listenSocket;
|
||||
}
|
||||
|
||||
error = bind(listenSocket, result.AddressInfo->ai_addr, (int)result.AddressInfo->ai_addrlen);
|
||||
if (error == SOCKET_ERROR)
|
||||
{
|
||||
SDV_LOG_ERROR("bind failed with error: ", std::to_string(WSAGetLastError()));
|
||||
closesocket(listenSocket);
|
||||
listenSocket = INVALID_SOCKET;
|
||||
return listenSocket;
|
||||
}
|
||||
|
||||
if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR)
|
||||
{
|
||||
SDV_LOG_ERROR("listen to SOCKET failed: ", std::to_string(WSAGetLastError()));
|
||||
closesocket(listenSocket);
|
||||
listenSocket = INVALID_SOCKET;
|
||||
return listenSocket;
|
||||
}
|
||||
|
||||
return listenSocket;
|
||||
}
|
||||
|
||||
|
||||
SocketConnection CSocketsChannelMgnt::CreateConnectedSocketPair()
|
||||
{
|
||||
SocketConnection connection;
|
||||
|
||||
uint32_t port = 0;
|
||||
SOCKET listenSocket = Listen(getHints, port);
|
||||
|
||||
uint16_t portOfListenSocket = GetPort(listenSocket);
|
||||
auto future = std::async([listenSocket]() { return accept(listenSocket, NULL, NULL); });
|
||||
|
||||
connection.From = CreateAndConnectToSocket(getHints, "localhost", std::to_string(portOfListenSocket).c_str());
|
||||
|
||||
// Future::Get has to be called after the CreateAndConnect-Function
|
||||
connection.To = future.get();
|
||||
return connection;
|
||||
}
|
||||
198
sdv_services/ipc_sockets/channel_mgnt.h
Normal file
198
sdv_services/ipc_sockets/channel_mgnt.h
Normal file
@@ -0,0 +1,198 @@
|
||||
#ifndef CHANNEL_MGNT_H
|
||||
#define CHANNEL_MGNT_H
|
||||
|
||||
#include <support/component_impl.h>
|
||||
#include <interfaces/ipc.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The CAddrInfo structure is used by the getaddrinfo function to hold host address information.
|
||||
*/
|
||||
struct CAddrInfo
|
||||
{
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CAddrInfo() = default;
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CAddrInfo(const CAddrInfo&) = delete;
|
||||
/**
|
||||
* @brief Copy constructor
|
||||
*/
|
||||
CAddrInfo& operator=(const CAddrInfo&) = delete;
|
||||
/**
|
||||
* @brief Move constructor
|
||||
* @param[in] other Reference to the structure to move.
|
||||
*/
|
||||
CAddrInfo(CAddrInfo&& other) = delete;
|
||||
|
||||
/**
|
||||
* @brief Move operator.
|
||||
* @param[in] other Reference to the structure to move.
|
||||
* @return Returns reference to CAddrInfo structure
|
||||
*/
|
||||
CAddrInfo& operator=(CAddrInfo&& other) = delete;
|
||||
|
||||
~CAddrInfo()
|
||||
{
|
||||
freeaddrinfo(AddressInfo);
|
||||
}
|
||||
|
||||
addrinfo* AddressInfo{nullptr}; ///< The CAddrInfo structure holding host address information.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initial startup of winSock
|
||||
* @return Returns 0 in case of no error, otherwise the error code
|
||||
*/
|
||||
inline int StartUpWinSock()
|
||||
{
|
||||
static bool isInitialized = false;
|
||||
if (isInitialized)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
WSADATA wsaData;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Holds to sockets.
|
||||
* Used by the core process to create a connection between to shild processes
|
||||
*/
|
||||
struct SocketConnection
|
||||
{
|
||||
SOCKET From{ INVALID_SOCKET }; ///< socket from child process
|
||||
SOCKET To{ INVALID_SOCKET }; ///< socket to child process
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief IPC channel management class for the shared memory communication.
|
||||
*/
|
||||
class CSocketsChannelMgnt : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::ipc::ICreateEndpoint,
|
||||
public sdv::ipc::IChannelAccess
|
||||
{
|
||||
public:
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IChannelAccess)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::ICreateEndpoint)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("DefaultSocketsChannelControl")
|
||||
DECLARE_OBJECT_CLASS_ALIAS("RemoteChannelControl")
|
||||
DECLARE_DEFAULT_OBJECT_NAME("RemoteChannelControl")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Create IPC connection object and return the endpoint information. Overload of
|
||||
* sdv::ipc::ICreateEndpoint::CreateEndpoint.
|
||||
* @details The endpoints are generated using either a size and a name based on the interface and port number provided through
|
||||
* the channel configuration or if no configuration is supplied a randomly generated size and name. The following configuration
|
||||
* can be supplied:
|
||||
* @code
|
||||
* [IpcChannel]
|
||||
* Interface = "127.0.0.1"
|
||||
* Port = 2000
|
||||
* @endcode
|
||||
* @param[in] ssChannelConfig Optional channel type specific endpoint configuration.
|
||||
* @return IPC connection object
|
||||
*/
|
||||
sdv::ipc::SChannelEndpoint CreateEndpoint(/*in*/ const sdv::u8string& ssChannelConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Create a connection object from the channel connection parameters string
|
||||
* @param[in] ssConnectString Reference to the string containing the channel connection parameters.
|
||||
* @return Pointer to IInterfaceAccess interface of the connection object or NULL when the object cannot be created.
|
||||
*/
|
||||
sdv::IInterfaceAccess* Access(const sdv::u8string& ssConnectString) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Creates a listen socket without setting the port and configured the socket to return directly without
|
||||
* blocking. No blocking access when data is received. Therefore any receive function must use polling
|
||||
* @return Returns the listen socket
|
||||
*/
|
||||
SOCKET CreateSocket(const addrinfo& hints);
|
||||
|
||||
/**
|
||||
* @brief get port number of a given socket
|
||||
* @param[in] socket the port number is requested
|
||||
* @return Returns the port number of the socket
|
||||
*/
|
||||
uint16_t GetPort(SOCKET socket) const;
|
||||
|
||||
SocketConnection CreateConnectedSocketPair();
|
||||
SOCKET Listen(const addrinfo& hints, uint32_t port);
|
||||
SOCKET CreateAndConnectToSocket(const addrinfo& hints, const char* defaultHost, const char* defaultPort);
|
||||
|
||||
/**
|
||||
* @brief Creates an own socket and connects to an existing socket
|
||||
* @param[in] hints The CAddrInfo structure to create the socket.
|
||||
* @param[in] hostName host name.
|
||||
* @param[in] portName port name.
|
||||
* @return Returns an socket
|
||||
*/
|
||||
SOCKET CreateAndConnectToExistingSocket(const addrinfo& hints, const char* hostName, const char* portName);
|
||||
|
||||
inline static const addrinfo getHints
|
||||
{
|
||||
[]() constexpr {
|
||||
addrinfo hints{0, 0, 0, 0, 0, nullptr, nullptr, nullptr};
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
hints.ai_next = nullptr;
|
||||
|
||||
return hints;
|
||||
}()
|
||||
};
|
||||
|
||||
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status.
|
||||
};
|
||||
DEFINE_SDV_OBJECT(CSocketsChannelMgnt)
|
||||
|
||||
#endif // ! defined CHANNEL_MGNT_H
|
||||
353
sdv_services/ipc_sockets/connection.cpp
Normal file
353
sdv_services/ipc_sockets/connection.cpp
Normal file
@@ -0,0 +1,353 @@
|
||||
#include "connection.h"
|
||||
|
||||
CConnection::CConnection(SOCKET preconfiguredSocket, bool acceptConnectionRequired)
|
||||
{
|
||||
std::fill(std::begin(m_SendBuffer), std::end(m_SendBuffer), '\0');
|
||||
std::fill(std::begin(m_ReceiveBuffer), std::end(m_ReceiveBuffer), '\0');
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::uninitialized;
|
||||
m_ConnectionSocket = preconfiguredSocket;
|
||||
m_AcceptConnectionRequired = acceptConnectionRequired;
|
||||
}
|
||||
|
||||
int32_t CConnection::Send(const char* data, int32_t dataLength)
|
||||
{
|
||||
int32_t bytesSent = send(m_ConnectionSocket, data, dataLength, 0);
|
||||
if (bytesSent == SOCKET_ERROR)
|
||||
{
|
||||
SDV_LOG_ERROR("send failed with error: ", std::to_string(WSAGetLastError()));
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::communication_error;
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
return bytesSent;
|
||||
}
|
||||
|
||||
bool CConnection::SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData)
|
||||
{
|
||||
static uint32_t msgId = 0;
|
||||
msgId++;
|
||||
size_t requiredSize = 0;
|
||||
std::for_each(seqData.cbegin(),
|
||||
seqData.cend(),
|
||||
[&](const sdv::pointer<uint8_t>& rBuffer) { requiredSize += rBuffer.size(); });
|
||||
|
||||
std::unique_lock<std::recursive_mutex> lock(m_SendMutex);
|
||||
|
||||
uint32_t packageNumber = 1;
|
||||
SMsgHeader msgHeader;
|
||||
msgHeader.msgStart = m_MsgStart;
|
||||
msgHeader.msgEnd = m_MsgEnd;
|
||||
msgHeader.msgId = msgId;
|
||||
msgHeader.msgSize = static_cast<uint32_t>(requiredSize);
|
||||
msgHeader.packetNumber = packageNumber;
|
||||
msgHeader.totalPacketCount = static_cast<uint32_t>((requiredSize + m_SendMessageSize - 1) / m_SendMessageSize);
|
||||
memcpy_s(&m_SendBuffer[0], m_SendBufferSize, &msgHeader, sizeof(msgHeader));
|
||||
uint32_t offsetBuffer = sizeof(SMsgHeader);
|
||||
|
||||
std::for_each(seqData.cbegin(),
|
||||
seqData.cend(),
|
||||
[&](const sdv::pointer<uint8_t>& rBuffer)
|
||||
{
|
||||
uint32_t offsetData = 0;
|
||||
while (rBuffer.size() > offsetData)
|
||||
{
|
||||
if (offsetBuffer == 0)
|
||||
{
|
||||
msgHeader.packetNumber = packageNumber;
|
||||
memcpy_s(&m_SendBuffer[0], m_SendBufferSize, &msgHeader, sizeof(msgHeader));
|
||||
offsetBuffer = sizeof(SMsgHeader);
|
||||
}
|
||||
|
||||
auto availableBufferSize = m_SendBufferSize - offsetBuffer;
|
||||
|
||||
if (availableBufferSize > (rBuffer.size() - offsetData))
|
||||
{
|
||||
// fragments fits in buffer, go to next fragment without sending
|
||||
memcpy_s(&m_SendBuffer[offsetBuffer],
|
||||
availableBufferSize,
|
||||
rBuffer.get() + offsetData,
|
||||
rBuffer.size() - offsetData);
|
||||
|
||||
offsetBuffer += (static_cast<uint32_t>(rBuffer.size()) - offsetData);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// fragments exceeds buffer, fill buffer, send buffer, keep fragment
|
||||
memcpy_s(&m_SendBuffer[offsetBuffer],
|
||||
availableBufferSize,
|
||||
rBuffer.get() + offsetData,
|
||||
availableBufferSize);
|
||||
|
||||
Send(m_SendBuffer, static_cast<int>(m_SendBufferSize));
|
||||
offsetData += (static_cast<uint32_t>(availableBufferSize));
|
||||
offsetBuffer = 0;
|
||||
packageNumber++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (0 != offsetBuffer)
|
||||
{
|
||||
Send(m_SendBuffer, static_cast<int>(offsetBuffer));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SOCKET CConnection::AcceptConnection()
|
||||
{
|
||||
SOCKET clientSocket{INVALID_SOCKET};
|
||||
|
||||
int32_t attempt = 0;
|
||||
while (clientSocket == INVALID_SOCKET)
|
||||
{
|
||||
clientSocket = accept(m_ConnectionSocket, NULL, NULL);
|
||||
if (clientSocket == INVALID_SOCKET)
|
||||
{
|
||||
Sleep(2);
|
||||
}
|
||||
attempt++;
|
||||
if (attempt > 2000)
|
||||
{
|
||||
SDV_LOG_ERROR("Accept socket failed, loop exeeded.");
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::connection_error;
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return clientSocket;
|
||||
}
|
||||
|
||||
bool CConnection::AsyncConnect(sdv::IInterfaceAccess* pReceiver)
|
||||
{
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::initializing;
|
||||
if (m_AcceptConnectionRequired)
|
||||
{
|
||||
m_ConnectionSocket = AcceptConnection();
|
||||
}
|
||||
|
||||
if (m_ConnectionSocket == INVALID_SOCKET)
|
||||
{
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::connection_error;
|
||||
return false;
|
||||
}
|
||||
m_pReceiver = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IDataReceiveCallback>();
|
||||
m_pEvent = sdv::TInterfaceAccessPtr(pReceiver).GetInterface<sdv::ipc::IConnectEventCallback>();
|
||||
m_ReceiveThread = std::thread(std::bind(&CConnection::ReceiveMessages, this));
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::connected;
|
||||
|
||||
// TODO: Connection negotiation didn't take place... implement this!
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConnection::WaitForConnection(/*in*/ uint32_t /*uiWaitMs*/)
|
||||
{
|
||||
if (m_ConnectionStatus == sdv::ipc::EConnectStatus::connected) return true;
|
||||
|
||||
// TODO: Implementation here!
|
||||
|
||||
// TODO: Suppress static code analysis while there is no implementation yet.
|
||||
// cppcheck-suppress identicalConditionAfterEarlyExit
|
||||
return m_ConnectionStatus == sdv::ipc::EConnectStatus::connected;
|
||||
}
|
||||
|
||||
void CConnection::CancelWait()
|
||||
{
|
||||
// TODO: Implementation here!
|
||||
}
|
||||
|
||||
void CConnection::Disconnect()
|
||||
{
|
||||
// TODO: Implementation here!
|
||||
}
|
||||
|
||||
uint64_t CConnection::RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* /*pEventCallback*/)
|
||||
{
|
||||
// TODO: Implementation here!
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CConnection::UnregisterStatusEventCallback(/*in*/ uint64_t /*uiCookie*/)
|
||||
{
|
||||
// TODO: Implementation here!
|
||||
}
|
||||
|
||||
sdv::ipc::EConnectStatus CConnection::GetStatus() const
|
||||
{
|
||||
return m_ConnectionStatus;
|
||||
}
|
||||
|
||||
void CConnection::DestroyObject()
|
||||
{
|
||||
m_StopReceiveThread = true;
|
||||
if (m_ReceiveThread.joinable())
|
||||
{
|
||||
m_ReceiveThread.join();
|
||||
}
|
||||
closesocket(m_ConnectionSocket);
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::disconnected;
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
bool CConnection::ValidateHeader(const SMsgHeader& msgHeader)
|
||||
{
|
||||
if ((msgHeader.msgStart == m_MsgStart) && (msgHeader.msgEnd == m_MsgEnd))
|
||||
{
|
||||
if (msgHeader.msgSize != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool CConnection::ReadNumberOfBytes(char* buffer, uint32_t bufferLength)
|
||||
{
|
||||
uint32_t bytesReceived = 0;
|
||||
while (!m_StopReceiveThread && (bufferLength > bytesReceived))
|
||||
{
|
||||
bytesReceived += recv(m_ConnectionSocket, buffer + bytesReceived, bufferLength - bytesReceived, 0);
|
||||
if (bytesReceived == static_cast<uint32_t>(SOCKET_ERROR))
|
||||
{
|
||||
auto error = WSAGetLastError();
|
||||
if (error != WSAEWOULDBLOCK)
|
||||
{
|
||||
if(error == WSAECONNRESET)
|
||||
{
|
||||
SDV_LOG_INFO("Reset SOCKET, recv() failed with error: ", error, " ", m_StopReceiveThread);
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::disconnected;
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
break;
|
||||
}
|
||||
else if (!m_StopReceiveThread)
|
||||
{
|
||||
SDV_LOG_ERROR("SOCKET_ERROR, recv() failed with error: ", error);
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::communication_error;
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
break;
|
||||
}
|
||||
}
|
||||
bytesReceived = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (bufferLength != bytesReceived)
|
||||
{
|
||||
if (!m_StopReceiveThread || (m_ConnectionStatus == sdv::ipc::EConnectStatus::disconnected))
|
||||
{
|
||||
SDV_LOG_INFO("The expected bytes could not be received.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CConnection::ReadMessageHeader(uint32_t& msgSize, uint32_t& msgId, uint32_t& packageNumber, uint32_t& totalPackageCount, bool verifyHeader)
|
||||
{
|
||||
SMsgHeader msgHeader;
|
||||
|
||||
if (ReadNumberOfBytes(reinterpret_cast<char*>(&msgHeader), sizeof(SMsgHeader)))
|
||||
{
|
||||
if (ValidateHeader(msgHeader))
|
||||
{
|
||||
if (!verifyHeader)
|
||||
{
|
||||
if (msgHeader.msgSize == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
msgSize = msgHeader.msgSize;
|
||||
msgId = msgHeader.msgId;
|
||||
packageNumber = msgHeader.packetNumber;
|
||||
totalPackageCount = msgHeader.totalPacketCount;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((msgId == msgHeader.msgId) && (packageNumber == msgHeader.packetNumber))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SDV_LOG_WARNING("Received wrong message, Id = ", std::to_string(msgHeader.msgId), " package = ",
|
||||
std::to_string(packageNumber), " (expected id = ", std::to_string(msgId), " package = ", std::to_string(packageNumber), ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_StopReceiveThread)
|
||||
{
|
||||
SDV_LOG_WARNING("Could not read message header");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CConnection::ReceiveMessages()
|
||||
{
|
||||
while (!m_StopReceiveThread && (m_ConnectionSocket != INVALID_SOCKET))
|
||||
{
|
||||
uint32_t messageSize{ 0 };
|
||||
uint32_t msgId{ 0 };
|
||||
uint32_t packageNumber{ 0 };
|
||||
uint32_t totalPackageCount{ 0 };
|
||||
|
||||
if (ReadMessageHeader(messageSize, msgId, packageNumber, totalPackageCount, false))
|
||||
{
|
||||
uint32_t dataOffset{ 0 };
|
||||
sdv::pointer<uint8_t> message;
|
||||
message.resize(messageSize);
|
||||
for (uint32_t package = 1; package <= totalPackageCount; package++)
|
||||
{
|
||||
uint32_t bytesToBeRead = m_SendMessageSize;
|
||||
if (package == totalPackageCount)
|
||||
{
|
||||
bytesToBeRead = messageSize - dataOffset; // last package
|
||||
}
|
||||
|
||||
if (package != 1)
|
||||
{
|
||||
if (!ReadMessageHeader(messageSize, msgId, package, totalPackageCount, true))
|
||||
{
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::communication_error;
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ReadNumberOfBytes(reinterpret_cast<char*>(message.get()) + dataOffset, bytesToBeRead))
|
||||
{
|
||||
m_ConnectionStatus = sdv::ipc::EConnectStatus::communication_error;
|
||||
m_ConnectionSocket = INVALID_SOCKET;
|
||||
break;
|
||||
}
|
||||
|
||||
dataOffset += bytesToBeRead;
|
||||
}
|
||||
|
||||
if (!m_StopReceiveThread && (m_pReceiver != nullptr) && (m_ConnectionSocket != INVALID_SOCKET)) // In case of shutdown the message maybe invalid/incomplete
|
||||
{
|
||||
//m_pReceiver->ReceiveData(message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
204
sdv_services/ipc_sockets/connection.h
Normal file
204
sdv_services/ipc_sockets/connection.h
Normal file
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* @file connection.h
|
||||
* @author Sudipta Babu Durjoy FRD DISS21 (mailto:sudipta.durjoy@zf.com)
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2023-04-18
|
||||
*
|
||||
* @copyright Copyright ZF Friedrichshafen AG (c) 2023
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CHANNEL_H
|
||||
#define CHANNEL_H
|
||||
|
||||
#include <thread>
|
||||
#include <algorithm>
|
||||
|
||||
#include <interfaces/ipc.h>
|
||||
#include <support/interface_ptr.h>
|
||||
#include <support/local_service_access.h>
|
||||
#include <support/component_impl.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
#endif
|
||||
|
||||
constexpr uint32_t m_MsgStart = 0x01020304; ///< value to mark the start of the message header
|
||||
constexpr uint32_t m_MsgEnd = 0x05060708; ///< value to mark the end of the message header
|
||||
|
||||
/**
|
||||
* @brief Message header which will be put before a message.
|
||||
* Can be used for validation and includes complete size of the message. Other values are not used yet
|
||||
*/
|
||||
struct SMsgHeader
|
||||
{
|
||||
uint32_t msgStart = 0; ///< marker for the start of the header
|
||||
uint32_t msgId = 0; ///< message Id, must match for all message packages
|
||||
uint32_t msgSize = 0; ///< size of the message without the header
|
||||
uint32_t packetNumber = 0; ///< number of the package starting with 1
|
||||
uint32_t totalPacketCount = 0; ///< total number of paackes required for the message
|
||||
uint32_t msgEnd = 0; ///< marker for the end of the header
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for remote IPC connection
|
||||
* Created and managed by IPCAccess::AccessRemoveIPCConnection(best use unique_ptr to store, so memory address stays
|
||||
* valid)
|
||||
*/
|
||||
class CConnection : public sdv::IInterfaceAccess, public sdv::ipc::IDataSend, public sdv::ipc::IConnect, public sdv::IObjectDestroy
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief default constructor used by create endpoint - allocates new buffers m_Sender and m_Receiver
|
||||
*/
|
||||
CConnection();
|
||||
|
||||
/**
|
||||
* @brief access existing connection
|
||||
* @param[in] preconfiguredSocket Prepared socket for the connection.
|
||||
* @param[in] acceptConnectionRequired If true connection has to be accepted before receive thread can be started.
|
||||
*/
|
||||
CConnection(SOCKET preconfiguredSocket, bool acceptConnectionRequired);
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor needed for "delete this;".
|
||||
*/
|
||||
virtual ~CConnection() = default;
|
||||
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IDataSend)
|
||||
SDV_INTERFACE_ENTRY(sdv::ipc::IConnect)
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
/**
|
||||
* @brief Sends data consisting of multiple data chunks via the IPC connection.
|
||||
* Overload of sdv::ipc::IDataSend::SendData.
|
||||
* @param[inout] seqData Sequence of data buffers to be sent. The sequence might be changed to optimize the communication
|
||||
* without having to copy the data.
|
||||
* @return Return 'true' if all data could be sent; 'false' otherwise.
|
||||
*/
|
||||
virtual bool SendData(/*inout*/ sdv::sequence<sdv::pointer<uint8_t>>& seqData) override;
|
||||
|
||||
/**
|
||||
* @brief Establish a connection and start sending/receiving messages. Overload of
|
||||
* sdv::ipc::IConnect::AsyncConnect.
|
||||
* @param[in] pReceiver The message has to be forwarded.
|
||||
* @return Returns 'true' when a connection could be established. Use IConnectStatus or IConnectEventCallback to check the
|
||||
* connection state.
|
||||
*/
|
||||
virtual bool AsyncConnect(/*in*/ sdv::IInterfaceAccess* pReceiver) override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a connection to take place. Overload of sdv::ipc::IConnect::WaitForConnection.
|
||||
* @param[in] uiWaitMs Wait for a connection to take place. A value of 0 doesn't wait at all, a value of 0xffffffff
|
||||
* waits for infinite time.
|
||||
* @return Returns 'true' when a connection took place.
|
||||
*/
|
||||
virtual bool WaitForConnection(/*in*/ uint32_t uiWaitMs) override;
|
||||
|
||||
/**
|
||||
* @brief Cancel a wait for connection. Overload of sdv::ipc::IConnect::CancelWait.
|
||||
*/
|
||||
virtual void CancelWait() override;
|
||||
|
||||
/**
|
||||
* @brief Disconnect from a connection. This will set the connect status to disconnected and release the interface
|
||||
* used for the status events.
|
||||
*/
|
||||
virtual void Disconnect() override;
|
||||
|
||||
/**
|
||||
* @brief Register event callback interface. Overload of sdv::ipc::IConnect::RegisterStatusEventCallback.
|
||||
* @details Register a connection status event callback interface. The exposed interface must be of type
|
||||
* IConnectEventCallback. The registration will exist until a call to the unregister function with the returned cookie
|
||||
* or until the connection is terminated.
|
||||
* @param[in] pEventCallback Pointer to the object exposing the IConnectEventCallback interface.
|
||||
* @return The cookie assigned to the registration.
|
||||
*/
|
||||
virtual uint64_t RegisterStatusEventCallback(/*in*/ sdv::IInterfaceAccess* pEventCallback) override;
|
||||
|
||||
/**
|
||||
* @brief Unregister the status event callback with the returned cookie from the registration. Overload of
|
||||
* sdv::ipc::IConnect::UnregisterStatusEventCallback.
|
||||
* @param[in] uiCookie The cookie returned by a previous call to the registration function.
|
||||
*/
|
||||
virtual void UnregisterStatusEventCallback(/*in*/ uint64_t uiCookie) override;
|
||||
|
||||
/**
|
||||
* @brief Get status of the connection
|
||||
* @return Returns the ipc::EConnectStatus struct
|
||||
*/
|
||||
virtual sdv::ipc::EConnectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Destroy the object. Overload of IObjectDestroy::DestroyObject.
|
||||
* @attention After a call of this function, all exposed interfaces render invalid and should not be used any more.
|
||||
*/
|
||||
virtual void DestroyObject() override;
|
||||
|
||||
private:
|
||||
std::thread m_ReceiveThread; ///< Thread which receives data from the socket
|
||||
std::atomic<bool> m_StopReceiveThread = false; ///< bool variable to stop thread
|
||||
std::atomic<sdv::ipc::EConnectStatus> m_ConnectionStatus; ///< the status of the connection
|
||||
SOCKET m_ConnectionSocket; ///< The socket to send and receive data
|
||||
sdv::ipc::IDataReceiveCallback* m_pReceiver = nullptr; ///< Receiver to pass the messages if available
|
||||
sdv::ipc::IConnectEventCallback* m_pEvent = nullptr; ///< Event receiver.
|
||||
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.
|
||||
|
||||
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.
|
||||
char m_SendBuffer[m_SendBufferSize]; ///< send buffer length
|
||||
char m_ReceiveBuffer[sizeof(SMsgHeader)]; ///< receive buffer, just for reading the message header
|
||||
uint32_t m_ReceiveBufferLength = sizeof(SMsgHeader); ///< receive buffer length
|
||||
|
||||
/**
|
||||
* @brief Function to accept the connection to the client.
|
||||
* @return Returns the socket to receive data
|
||||
*/
|
||||
SOCKET AcceptConnection();
|
||||
|
||||
/**
|
||||
* @brief Send data function via socket.
|
||||
* @param[in] data to be send
|
||||
* @param[in] dataLength size of the data to be sent
|
||||
* @return Returns number of bytes which has been sent
|
||||
*/
|
||||
int32_t Send(const char* data, int32_t dataLength);
|
||||
|
||||
/**
|
||||
* @brief Function to receive data, runs in a thread
|
||||
*/
|
||||
void ReceiveMessages();
|
||||
|
||||
/**
|
||||
* @brief Validates the header of the message to determine if message is valid
|
||||
* @param[in] msgHeader filled message header structure
|
||||
* @return true if valid header was found, otherwise false
|
||||
*/
|
||||
bool ValidateHeader(const SMsgHeader& msgHeader);
|
||||
|
||||
/**
|
||||
* @brief read header and get the values from the header. In case verify == true validate the input values
|
||||
* @param[in] msgSize size of the message without headers
|
||||
* @param[in] msgId message id of the message
|
||||
* @param[in] packageNumber package number of the message
|
||||
* @param[in] totalPackageCount number of packages the message requires
|
||||
* @param[in] verifyHeader If true verify that the input of msgId and packageNumber match the header values
|
||||
* @return if verify == false, return true if a valid header can be read.
|
||||
* if verify == true input values of msdId and package number must match with the header values.
|
||||
*/
|
||||
bool ReadMessageHeader(uint32_t &msgSize, uint32_t &msgId, uint32_t &packageNumber, uint32_t &totalPackageCount, bool verifyHeader);
|
||||
|
||||
/**
|
||||
* @brief read number of bytes and write them to the given buffer
|
||||
* @param[in] buffer Buffer the data is stored
|
||||
* @param[in] length of the buffer to be filled
|
||||
* @return return true if the number of bytes can be read, otherwise false
|
||||
*/
|
||||
bool ReadNumberOfBytes(char* buffer, uint32_t length);
|
||||
};
|
||||
|
||||
#endif // !define CHANNEL_H
|
||||
22
sdv_services/manifest_util/CMakeLists.txt
Normal file
22
sdv_services/manifest_util/CMakeLists.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
# Define project
|
||||
project(manifest_util VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(manifest_util SHARED
|
||||
"manifest_util.h"
|
||||
"manifest_util.cpp")
|
||||
if (WIN32)
|
||||
target_link_libraries(manifest_util ${CMAKE_THREAD_LIBS_INIT} Rpcrt4.lib)
|
||||
elseif(UNIX)
|
||||
target_link_libraries(manifest_util rt ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
target_link_options(manifest_util PRIVATE)
|
||||
target_include_directories(manifest_util PRIVATE ./include/)
|
||||
set_target_properties(manifest_util PROPERTIES PREFIX "")
|
||||
set_target_properties(manifest_util PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(manifest_util CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} manifest_util PARENT_SCOPE)
|
||||
70
sdv_services/manifest_util/manifest_util.cpp
Normal file
70
sdv_services/manifest_util/manifest_util.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "manifest_util.h"
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
#pragma push_macro("GetObject")
|
||||
#undef GetObject
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#elif defined __unix__
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
sdv::u8string CManifestUtil::ReadModuleManifest(/*in*/ const sdv::u8string& ssModule)
|
||||
{
|
||||
sdv::u8string ssManifest;
|
||||
std::filesystem::path pathModule(static_cast<std::string>(ssModule));
|
||||
if (!std::filesystem::exists(pathModule)) return ssManifest;
|
||||
|
||||
// Load the module
|
||||
#ifdef _WIN32
|
||||
sdv::core::TModuleID tModuleID = reinterpret_cast<sdv::core::TModuleID>(LoadLibrary(pathModule.native().c_str()));
|
||||
#elif defined __unix__
|
||||
sdv::core::TModuleID tModuleID = reinterpret_cast<sdv::core::TModuleID>(dlopen(pathModule.native().c_str(), RTLD_LAZY));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
if (!tModuleID) return ssManifest;
|
||||
|
||||
// Check whether the module exposes the necessary functions
|
||||
using TFNHasActiveObjects = bool();
|
||||
using TFNGetModuleFactory = sdv::IInterfaceAccess*(uint32_t);
|
||||
using TFNGetManifest = const char*();
|
||||
|
||||
#ifdef _WIN32
|
||||
std::function<TFNGetModuleFactory> fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "GetModuleFactory"));
|
||||
std::function<TFNHasActiveObjects> fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "HasActiveObjects"));
|
||||
std::function<TFNGetManifest> fnGetManifest = reinterpret_cast<TFNGetManifest*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "GetManifest"));
|
||||
#elif defined __unix__
|
||||
std::function<TFNGetModuleFactory> fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(dlsym(reinterpret_cast<void*>(tModuleID), "GetModuleFactory"));
|
||||
std::function<TFNHasActiveObjects> fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(dlsym(reinterpret_cast<void*>(tModuleID), "HasActiveObjects"));
|
||||
std::function<TFNGetManifest> fnGetManifest = reinterpret_cast<TFNGetManifest*>(dlsym(reinterpret_cast<void*>(tModuleID), "GetManifest"));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
|
||||
// Check for functions and the correct version
|
||||
if (fnGetFactory && fnActiveObjects && fnGetManifest && fnGetManifest())
|
||||
ssManifest = fnGetManifest();
|
||||
|
||||
// Release the library
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(reinterpret_cast<HMODULE>(tModuleID));
|
||||
#elif defined __unix__
|
||||
dlclose(reinterpret_cast<void*>(tModuleID));
|
||||
#else
|
||||
#error OS not supported!
|
||||
#endif
|
||||
|
||||
// Return the manifest
|
||||
return ssManifest;
|
||||
}
|
||||
46
sdv_services/manifest_util/manifest_util.h
Normal file
46
sdv_services/manifest_util/manifest_util.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @file process_control.h
|
||||
* @author Sudipta Babu Durjoy FRD DISS21 (mailto:sudipta.durjoy@zf.com)
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2023-10-23
|
||||
*
|
||||
* @copyright Copyright ZF Friedrichshafen AG (c) 2023
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MANIFEST_UTIL_H
|
||||
#define MANIFEST_UTIL_H
|
||||
|
||||
#include <interfaces/config.h>
|
||||
#include <support/component_impl.h>
|
||||
|
||||
/**
|
||||
* @brief Manifest helper utility class.
|
||||
* @details The manifest helper utility component allows extracting module information from the module. To do this, it needs to
|
||||
* load the module and access one of the functions. Since several modules are designed to run in an isolated context, loading
|
||||
* the module and extracting the manifest should also occur in an isolated context (hence as a separate utility and not part of
|
||||
* the core).
|
||||
*/
|
||||
class CManifestUtil : public sdv::CSdvObject, public sdv::helper::IModuleManifestHelper
|
||||
{
|
||||
public:
|
||||
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::helper::IModuleManifestHelper)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::Utility)
|
||||
DECLARE_OBJECT_CLASS_NAME("ManifestHelperUtility")
|
||||
|
||||
/**
|
||||
* @brief Read the module manifest. Overload of sdv::helper::IModuleManifestHelper::ReadModuleManifest.
|
||||
* @param[in] ssModule Path to the module file.
|
||||
* @return The module manifest if available. Otherwise an empty string.
|
||||
*/
|
||||
virtual sdv::u8string ReadModuleManifest(/*in*/ const sdv::u8string& ssModule) override;
|
||||
};
|
||||
DEFINE_SDV_OBJECT(CManifestUtil)
|
||||
|
||||
#endif // !define MANIFEST_UTIL_H
|
||||
19
sdv_services/process_control/CMakeLists.txt
Normal file
19
sdv_services/process_control/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
# Define project
|
||||
project(process_control VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(process_control SHARED
|
||||
"process_control.h"
|
||||
"process_control.cpp")
|
||||
target_link_options(process_control PRIVATE)
|
||||
target_include_directories(process_control PRIVATE ./include/)
|
||||
target_link_libraries(process_control ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
set_target_properties(process_control PROPERTIES PREFIX "")
|
||||
set_target_properties(process_control PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(process_control CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} process_control PARENT_SCOPE)
|
||||
591
sdv_services/process_control/process_control.cpp
Normal file
591
sdv_services/process_control/process_control.cpp
Normal file
@@ -0,0 +1,591 @@
|
||||
#include "process_control.h"
|
||||
#include "../../global/base64.h"
|
||||
#include "../../global/exec_dir_helper.h"
|
||||
#include <support/local_service_access.h>
|
||||
#include <interfaces/app.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// Resolve conflict
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <objbase.h>
|
||||
#include <wchar.h>
|
||||
|
||||
// Resolve conflict
|
||||
#pragma pop_macro("interface")
|
||||
#ifdef GetClassInfo
|
||||
#undef GetClassInfo
|
||||
#endif
|
||||
#elif defined __unix__
|
||||
//#include <spawn.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include <spawn.h>
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
#include "../../global/trace.h"
|
||||
|
||||
CProcessControl::~CProcessControl()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void CProcessControl::Initialize(const sdv::u8string& /*ssObjectConfig*/)
|
||||
{
|
||||
if (m_eObjectStatus != sdv::EObjectStatus::initialization_pending) return;
|
||||
|
||||
// Without monitor no trigger...
|
||||
m_threadMonitor = std::thread(&CProcessControl::MonitorThread, this);
|
||||
|
||||
m_eObjectStatus = sdv::EObjectStatus::initialized;
|
||||
}
|
||||
|
||||
sdv::EObjectStatus CProcessControl::GetStatus() const
|
||||
{
|
||||
return m_eObjectStatus;
|
||||
}
|
||||
|
||||
void CProcessControl::SetOperationMode(sdv::EOperationMode eMode)
|
||||
{
|
||||
switch (eMode)
|
||||
{
|
||||
case sdv::EOperationMode::configuring:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::running || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::configuring;
|
||||
break;
|
||||
case sdv::EOperationMode::running:
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::configuring || m_eObjectStatus == sdv::EObjectStatus::initialized)
|
||||
m_eObjectStatus = sdv::EObjectStatus::running;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CProcessControl::Shutdown()
|
||||
{
|
||||
// TODO: Close process handles
|
||||
if (m_eObjectStatus == sdv::EObjectStatus::destruction_pending) return;
|
||||
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
|
||||
|
||||
// Shutdown the monitor
|
||||
m_bShutdown = true;
|
||||
if (m_threadMonitor.joinable())
|
||||
m_threadMonitor.join();
|
||||
|
||||
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
|
||||
}
|
||||
|
||||
bool CProcessControl::AllowProcessControl() const
|
||||
{
|
||||
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 ||
|
||||
pAppContext->GetContextType() == sdv::app::EAppContext::essential);
|
||||
}
|
||||
|
||||
sdv::process::TProcessID CProcessControl::GetProcessID() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return static_cast<uint64_t>(GetCurrentProcessId());
|
||||
#elif defined __unix__
|
||||
return static_cast<uint64_t>(getpid());
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t CProcessControl::RegisterMonitor(/*in*/ sdv::process::TProcessID tProcessID, /*in*/ sdv::IInterfaceAccess* pMonitor)
|
||||
{
|
||||
if (!tProcessID || !pMonitor) return 0;
|
||||
sdv::process::IProcessLifetimeCallback* pCallback =
|
||||
sdv::TInterfaceAccessPtr(pMonitor).GetInterface<sdv::process::IProcessLifetimeCallback>();
|
||||
if (!pCallback) return 0;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxProcesses);
|
||||
|
||||
SDV_LOG_TRACE(GetTimestamp(), "Registering... (PID#", tProcessID, ")");
|
||||
|
||||
// Find the process
|
||||
auto itProcess = m_mapProcesses.find(tProcessID);
|
||||
if (itProcess == m_mapProcesses.end())
|
||||
{
|
||||
#if _WIN32
|
||||
// Get the process handle.
|
||||
HANDLE hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION, FALSE, static_cast<DWORD>(tProcessID));
|
||||
if (!hProcess) return 0; // Cannot monitor
|
||||
#elif defined __unix__
|
||||
// Use "kill" to detect the existence of the process
|
||||
// Notice: this doesn't kill the process.
|
||||
if (kill(tProcessID, 0) != 0) return 0; // Cannot monitor
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
// Create a new process structure
|
||||
auto ptrNewProcess = std::make_shared<SProcessHelper>();
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
|
||||
// exception was triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrNewProcess)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CloseHandle(hProcess);
|
||||
#endif
|
||||
SDV_LOG_TRACE(GetTimestamp(), "Could not add monitor... (PID#", tProcessID, ")");
|
||||
return 0;
|
||||
}
|
||||
ptrNewProcess->tProcessID = tProcessID;
|
||||
#ifdef _WIN32
|
||||
ptrNewProcess->hProcess = hProcess;
|
||||
#endif
|
||||
auto prInsert = m_mapProcesses.insert(std::make_pair(tProcessID, ptrNewProcess));
|
||||
if (!prInsert.second)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
CloseHandle(hProcess);
|
||||
#endif
|
||||
SDV_LOG_TRACE(GetTimestamp(), "Could not add monitor... (PID#", tProcessID, ")");
|
||||
return 0; // Could not insert
|
||||
}
|
||||
SDV_LOG_TRACE(GetTimestamp(), "Monitor now added... (PID#", tProcessID, ")");
|
||||
itProcess = prInsert.first;
|
||||
}
|
||||
auto ptrProcess = itProcess->second;
|
||||
|
||||
// Create a new cookie
|
||||
uint32_t uiCookie = m_uiNextMonCookie++;
|
||||
|
||||
// Register the new monitor
|
||||
m_mapMonitors[uiCookie] = ptrProcess;
|
||||
lock.unlock();
|
||||
|
||||
// Add the monitor to the process as well
|
||||
std::unique_lock<std::mutex> lockProcess(ptrProcess->mtxProcess);
|
||||
ptrProcess->mapAssociatedMonitors[uiCookie] = pCallback;
|
||||
|
||||
// Monitor thread running?
|
||||
// Already terminated?
|
||||
if (!ptrProcess->bRunning)
|
||||
pCallback->ProcessTerminated(tProcessID, ptrProcess->iRetVal);
|
||||
|
||||
return uiCookie;
|
||||
}
|
||||
|
||||
void CProcessControl::UnregisterMonitor(/*in*/ uint32_t uiCookie)
|
||||
{
|
||||
if (!uiCookie) return;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxProcesses);
|
||||
|
||||
// Find the monitor and remove the monitor
|
||||
auto itMonitor = m_mapMonitors.find(uiCookie);
|
||||
if (itMonitor == m_mapMonitors.end()) return;
|
||||
auto ptrProcess = itMonitor->second;
|
||||
m_mapMonitors.erase(itMonitor);
|
||||
|
||||
lock.unlock();
|
||||
|
||||
// Removing the monitor from the map might be problematic when the call is done in a callback. Simply update the pointer
|
||||
// instead (this will not affect the map order).
|
||||
std::unique_lock<std::mutex> lockProcess(ptrProcess->mtxProcess);
|
||||
ptrProcess->mapAssociatedMonitors.erase(uiCookie);
|
||||
}
|
||||
|
||||
bool CProcessControl::WaitForTerminate(/*in*/ sdv::process::TProcessID tProcessID, /*in*/ uint32_t uiWaitMs)
|
||||
{
|
||||
if (!tProcessID) return true; // Non-existent processes are terminated :-)
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mtxProcesses);
|
||||
|
||||
// Find the process
|
||||
auto itProcess = m_mapProcesses.find(tProcessID);
|
||||
if (itProcess == m_mapProcesses.end()) return true; // Non-existent processes are terminated :-)
|
||||
auto ptrProcess = itProcess->second;
|
||||
|
||||
lock.unlock();
|
||||
|
||||
// Already terminated?
|
||||
std::unique_lock<std::mutex> lockProcess(ptrProcess->mtxProcess);
|
||||
if (!ptrProcess->bRunning) return true;
|
||||
|
||||
// Wait for termination
|
||||
std::chrono::high_resolution_clock::time_point tpStart = std::chrono::high_resolution_clock::now();
|
||||
bool bTimeout = false;
|
||||
// False warning from cppcheck. bRunning is set by the monitor thread. Suppress warning.
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
while (ptrProcess->bRunning)
|
||||
{
|
||||
ptrProcess->cvWaitForProcess.wait_for(lockProcess, std::chrono::milliseconds(10));
|
||||
if (std::chrono::duration<double, std::milli>(std::chrono::high_resolution_clock::now() - tpStart).count() > static_cast<double>(uiWaitMs))
|
||||
{
|
||||
bTimeout = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return !bTimeout;
|
||||
}
|
||||
|
||||
sdv::process::TProcessID CProcessControl::Execute(/*in*/ const sdv::u8string& ssModule,
|
||||
/*in*/ const sdv::sequence<sdv::u8string>& seqArgs, /*in*/ sdv::process::EProcessRights eRights)
|
||||
{
|
||||
if (!AllowProcessControl()) return 0;
|
||||
if (ssModule.empty()) return 0;
|
||||
sdv::process::TProcessID tProcessID = 0;
|
||||
|
||||
// Update rights
|
||||
bool bReduceRights = false;
|
||||
const sdv::app::IAppContext* pAppContext = sdv::core::GetCore<sdv::app::IAppContext>();
|
||||
if (!pAppContext) return 0;
|
||||
|
||||
switch (eRights)
|
||||
{
|
||||
case sdv::process::EProcessRights::default_rights:
|
||||
// Default implementation would be reduced rights
|
||||
bReduceRights = pAppContext->GetContextType() == sdv::app::EAppContext::main;
|
||||
break;
|
||||
case sdv::process::EProcessRights::reduced_rights:
|
||||
bReduceRights = true;
|
||||
break;
|
||||
case sdv::process::EProcessRights::parent_rights:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for the existence of the module
|
||||
std::filesystem::path pathModule(static_cast<std::string>(ssModule));
|
||||
#ifdef _WIN32
|
||||
if (!pathModule.has_extension())
|
||||
pathModule.replace_extension(".exe");
|
||||
#endif
|
||||
if (pathModule.is_relative())
|
||||
{
|
||||
// Get the module serch dirs
|
||||
sdv::sequence<sdv::u8string> seqSearchDirs;
|
||||
const sdv::core::IModuleControlConfig* pModuleControlConfig = sdv::core::GetCore<sdv::core::IModuleControlConfig>();
|
||||
if (pModuleControlConfig)
|
||||
seqSearchDirs = pModuleControlConfig->GetModuleSearchDirs();
|
||||
|
||||
// Add the current directory as well.
|
||||
seqSearchDirs.insert(seqSearchDirs.begin(), ".");
|
||||
|
||||
// Now find the module
|
||||
bool bFound = false;
|
||||
for (const sdv::u8string& rssPath : seqSearchDirs)
|
||||
{
|
||||
std::filesystem::path pathModuleTemp = std::filesystem::path(static_cast<std::string>(rssPath)) / pathModule;
|
||||
if (std::filesystem::exists(pathModuleTemp))
|
||||
{
|
||||
pathModule = pathModuleTemp;
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Found?
|
||||
if (!bFound) return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// The command line is one string. If containing spaces, include quotes
|
||||
std::wstringstream sstreamCommandline;
|
||||
sstreamCommandline << pathModule.native();
|
||||
for (auto& rssArg : seqArgs)
|
||||
{
|
||||
sstreamCommandline << L" ";
|
||||
bool bQuotes = rssArg.find(" ") != std::string::npos;
|
||||
if (bQuotes) sstreamCommandline << L'\"';
|
||||
sstreamCommandline << sdv::MakeWString(rssArg);
|
||||
if (bQuotes) sstreamCommandline << L'\"';
|
||||
}
|
||||
|
||||
// Command line string
|
||||
std::wstring ssCommandLine = sstreamCommandline.str();
|
||||
if (ssCommandLine.size() > 32768) return 0;
|
||||
ssCommandLine.reserve(32768);
|
||||
|
||||
/* First get a handle to the current process's primary token */
|
||||
HANDLE hProcessToken = 0;
|
||||
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &hProcessToken);
|
||||
|
||||
// Update rights
|
||||
if (bReduceRights)
|
||||
{
|
||||
// Create a restricted token with all privileges removed.
|
||||
// NOTE: This doesn't restrict file system access. To do so, use a separate user (e.g. guest user).
|
||||
HANDLE hRestrictedToken = 0;
|
||||
CreateRestrictedToken(hProcessToken, DISABLE_MAX_PRIVILEGE, 0, 0, 0, 0, 0, 0, &hRestrictedToken);
|
||||
CloseHandle(hProcessToken);
|
||||
hProcessToken = hRestrictedToken;
|
||||
}
|
||||
|
||||
// Start the process
|
||||
STARTUPINFOW sStartInfo{};
|
||||
PROCESS_INFORMATION sProcessInfo{};
|
||||
bool bRes = CreateProcessAsUserW(hProcessToken, // Process token
|
||||
nullptr, // No module name (use command line)
|
||||
&ssCommandLine.front(), // Command line
|
||||
nullptr, // Process handle not inheritable
|
||||
nullptr, // Thread handle not inheritable
|
||||
FALSE, // Set handle inheritance to FALSE
|
||||
0, // No creation flags
|
||||
nullptr, // Use parent's environment block
|
||||
GetExecDirectory().native().c_str(), // Use parent's starting directory
|
||||
&sStartInfo, // Pointer to STARTUPINFO structure
|
||||
&sProcessInfo); // Pointer to PROCESS_INFORMATION structure
|
||||
if (!bRes) return 0;
|
||||
tProcessID = sProcessInfo.dwProcessId;
|
||||
CloseHandle(sProcessInfo.hThread);
|
||||
CloseHandle(hProcessToken);
|
||||
|
||||
#elif defined __unix__
|
||||
|
||||
// Create the argument list
|
||||
std::vector<char*> vecArgs;
|
||||
auto seqTempArgs = seqArgs;
|
||||
std::string ssModuleTemp = pathModule.native();
|
||||
seqTempArgs.insert(seqTempArgs.begin(), &ssModuleTemp.front());
|
||||
for (auto& rssArg : seqTempArgs)
|
||||
vecArgs.push_back(&rssArg.front());
|
||||
vecArgs.push_back(nullptr);
|
||||
|
||||
// Create environment variable list
|
||||
std::vector<char*> vecEnv;
|
||||
vecEnv.push_back(nullptr);
|
||||
|
||||
// TODO: Update rights and environment vars
|
||||
|
||||
// Fork the process
|
||||
int pid = vfork();
|
||||
switch (pid)
|
||||
{
|
||||
case -1: // Error called in parent process; cannot continue
|
||||
return 0;
|
||||
case 0: // Child process is executing with pid == 0
|
||||
{
|
||||
// Reduce rights
|
||||
if (bReduceRights)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-result"
|
||||
#endif
|
||||
setgid(getgid());
|
||||
setuid(getuid());
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
execve(pathModule.native().c_str(), &vecArgs.front(), &vecEnv.front());
|
||||
std::abort(); // Child process only comes here on error
|
||||
}
|
||||
default:
|
||||
// Parent process is executing with pid != 0
|
||||
tProcessID = static_cast<sdv::process::TProcessID>(pid);
|
||||
break;
|
||||
}
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
// Create a new process structure
|
||||
auto ptrNewProcess = std::make_shared<SProcessHelper>();
|
||||
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
|
||||
// exception was triggered).
|
||||
// cppcheck-suppress knownConditionTrueFalse
|
||||
if (!ptrNewProcess)
|
||||
{
|
||||
// TODO Terminate process and close handles...
|
||||
return 0;
|
||||
}
|
||||
ptrNewProcess->tProcessID = tProcessID;
|
||||
#ifdef _WIN32
|
||||
ptrNewProcess->hProcess = sProcessInfo.hProcess;
|
||||
#endif
|
||||
m_mapProcesses[tProcessID] = ptrNewProcess;
|
||||
|
||||
return tProcessID;
|
||||
}
|
||||
|
||||
bool CProcessControl::Terminate(/*in*/ sdv::process::TProcessID tProcessID)
|
||||
{
|
||||
if (!AllowProcessControl()) return false;
|
||||
|
||||
if (!tProcessID) return false;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, static_cast<DWORD>(tProcessID));
|
||||
if (!hProcess) return 0; // Cannot terminate
|
||||
TerminateProcess(hProcess, static_cast<UINT>(-100));
|
||||
CloseHandle(hProcess);
|
||||
#elif defined __unix__
|
||||
// Send a signal (SIGUSR1) to the child process.
|
||||
if (kill(tProcessID, SIGUSR1) != 0) return false;
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CProcessControl::MonitorThread()
|
||||
{
|
||||
while (!m_bShutdown)
|
||||
{
|
||||
// Run through the list of processes and determine which process has been terminated.
|
||||
std::unique_lock<std::mutex> lock(m_mtxProcesses);
|
||||
std::vector<std::shared_ptr<SProcessHelper>> m_vecTerminatedProcesses;
|
||||
for (auto& vtProcess : m_mapProcesses)
|
||||
{
|
||||
// Update only once
|
||||
if (!vtProcess.second->bRunning) continue;
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD dwExitCode = 0;
|
||||
if (GetExitCodeProcess(vtProcess.second->hProcess, &dwExitCode) && dwExitCode != STILL_ACTIVE)
|
||||
// if (WaitForSingleObject(vtProcess.second->hProcess, 0) == WAIT_OBJECT_0)
|
||||
{
|
||||
// Get the exit code
|
||||
//DWORD dwExitCode = 0;
|
||||
//GetExitCodeProcess(vtProcess.second->hProcess, &dwExitCode);
|
||||
vtProcess.second->iRetVal = static_cast<int>(dwExitCode); // Do not convert to 64-bit!
|
||||
|
||||
// Close the process handle
|
||||
CloseHandle(vtProcess.second->hProcess);
|
||||
vtProcess.second->hProcess = 0;
|
||||
m_vecTerminatedProcesses.push_back(vtProcess.second);
|
||||
|
||||
TRACE(GetTimestamp(), "Adding process PID#", vtProcess.second->tProcessID, " to termination map with exit code ", vtProcess.second->iRetVal);
|
||||
}
|
||||
#elif defined __unix__
|
||||
// There are two ways of checking whether a process is still running. If the monitor process is the parent process, the
|
||||
// waitpid function should be used to request the process state. If the process was not created by the parent process,
|
||||
// the kill function returns information about the process.
|
||||
// After termination a process will stay dorment until the parent process has received the exit value of the process.
|
||||
// This is also the reason why the kill function is not working with the parent process, since it is only returning
|
||||
// a result after the process is not available at all any more (also not dorment).
|
||||
// Use a flag to indicate that the process is not a child process of the monitor process.
|
||||
if (!vtProcess.second->bNotAChild)
|
||||
{
|
||||
// Check with waitpid first
|
||||
int iStatus = 0;
|
||||
#ifdef WCONTINUED
|
||||
pid_t pid = waitpid(vtProcess.second->tProcessID, &iStatus, WCONTINUED | WNOHANG);
|
||||
#else
|
||||
pid_t pid = waitpid(vtProcess.second->tProcessID, &iStatus, WNOHANG);
|
||||
#endif
|
||||
switch (pid)
|
||||
{
|
||||
case -1:
|
||||
// Not a child process of the monitor process. Or a signal is returned to the calling process. Use kill instead.
|
||||
vtProcess.second->bNotAChild = true;
|
||||
SDV_LOG_TRACE(getpid(), " Process ", vtProcess.second->tProcessID, " is not child process...");
|
||||
break;
|
||||
case 0:
|
||||
// Process still running, no status available.
|
||||
break;
|
||||
default:
|
||||
if (WIFEXITED(iStatus))
|
||||
{
|
||||
m_vecTerminatedProcesses.push_back(vtProcess.second);
|
||||
vtProcess.second->iRetVal = static_cast<int8_t>(WEXITSTATUS(iStatus));
|
||||
SDV_LOG_TRACE(getpid(), " Normal exit detected for process ", vtProcess.second->tProcessID);
|
||||
}
|
||||
else if (WIFSIGNALED(iStatus))
|
||||
{
|
||||
m_vecTerminatedProcesses.push_back(vtProcess.second);
|
||||
// Note: The status is not reported by the process, since it was terminated without return value.
|
||||
//vtProcess.second->iRetVal = static_cast<int8_t>(WTERMSIG(iStatus));
|
||||
vtProcess.second->iRetVal = -100;
|
||||
SDV_LOG_TRACE(getpid(), " Signalled stop detected for process ", vtProcess.second->tProcessID);
|
||||
}
|
||||
else if (WIFSTOPPED(iStatus))
|
||||
{
|
||||
m_vecTerminatedProcesses.push_back(vtProcess.second);
|
||||
vtProcess.second->iRetVal = static_cast<int8_t>(WSTOPSIG(iStatus));
|
||||
SDV_LOG_TRACE(getpid(), " Terminate exit detected for process ", vtProcess.second->tProcessID);
|
||||
}
|
||||
SDV_LOG_TRACE(GetTimestamp(), "Exit detected with exit code ", vtProcess.second->iRetVal);
|
||||
}
|
||||
}
|
||||
|
||||
// For a process not being the child of the monitor process, check with the kill function.
|
||||
// Note: check for the bNotAChild flag once more, since it might be set by the waitpid analysis.
|
||||
if (vtProcess.second->bNotAChild)
|
||||
{
|
||||
// Use "kill" to detect the existence of the process
|
||||
// Notice: this doesn't kill the process.
|
||||
kill(vtProcess.first, 0);
|
||||
if (errno == ESRCH)
|
||||
{
|
||||
m_vecTerminatedProcesses.push_back(vtProcess.second);
|
||||
|
||||
SDV_LOG_TRACE(getpid(), " Non-child exit detected for process ", vtProcess.second->tProcessID);
|
||||
|
||||
// No exit code available...
|
||||
}
|
||||
}
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
}
|
||||
|
||||
// Allow changes
|
||||
lock.unlock();
|
||||
|
||||
// Is there a vector of terminated process IDs?
|
||||
for (auto ptrTerminatedProcess : m_vecTerminatedProcesses)
|
||||
{
|
||||
std::unique_lock<std::mutex> lockProcess(ptrTerminatedProcess->mtxProcess);
|
||||
|
||||
// Reset the running flag
|
||||
ptrTerminatedProcess->bRunning = false;
|
||||
|
||||
// Call the callback functions
|
||||
// Note: The unregister function might change the pointers in the map. Make a copy.
|
||||
auto mapAssociatedMonitorsCopy = ptrTerminatedProcess->mapAssociatedMonitors;
|
||||
lockProcess.unlock();
|
||||
for (auto& rvtMonitor : mapAssociatedMonitorsCopy)
|
||||
{
|
||||
// Check whether the monitor still exists
|
||||
lockProcess.lock();
|
||||
auto itMonitor = ptrTerminatedProcess->mapAssociatedMonitors.find(rvtMonitor.first);
|
||||
if (itMonitor == ptrTerminatedProcess->mapAssociatedMonitors.end() || !itMonitor->second)
|
||||
{
|
||||
// Monitor not available...
|
||||
lockProcess.unlock();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow updates to take place
|
||||
lockProcess.unlock();
|
||||
|
||||
// Call callback
|
||||
rvtMonitor.second->ProcessTerminated(ptrTerminatedProcess->tProcessID, ptrTerminatedProcess->iRetVal);
|
||||
}
|
||||
|
||||
// Inform all waiting processes
|
||||
ptrTerminatedProcess->cvWaitForProcess.notify_all();
|
||||
}
|
||||
|
||||
// Wait for 100ms until next check
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
|
||||
176
sdv_services/process_control/process_control.h
Normal file
176
sdv_services/process_control/process_control.h
Normal file
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* @file process_control.h
|
||||
* @author Erik Verhoeven
|
||||
* @brief
|
||||
* @version 1.0
|
||||
* @date 2024-08-16
|
||||
*
|
||||
* @copyright Copyright ZF Friedrichshafen AG (c) 2024
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PROCESS_CONTROL_H
|
||||
#define PROCESS_CONTROL_H
|
||||
|
||||
#include <interfaces/process.h>
|
||||
#include <support/component_impl.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <condition_variable>
|
||||
|
||||
/**
|
||||
* @brief Process control service class
|
||||
*/
|
||||
class CProcessControl : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::process::IProcessInfo,
|
||||
public sdv::process::IProcessLifetime, public sdv::process::IProcessControl
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*/
|
||||
CProcessControl() = default;
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
virtual ~CProcessControl() override;
|
||||
|
||||
// Interface map
|
||||
BEGIN_SDV_INTERFACE_MAP()
|
||||
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
|
||||
SDV_INTERFACE_ENTRY(sdv::process::IProcessInfo)
|
||||
SDV_INTERFACE_ENTRY(sdv::process::IProcessLifetime)
|
||||
SDV_INTERFACE_CHECK_CONDITION(AllowProcessControl())
|
||||
SDV_INTERFACE_ENTRY(sdv::process::IProcessControl)
|
||||
END_SDV_INTERFACE_MAP()
|
||||
|
||||
// Object declarations
|
||||
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
|
||||
DECLARE_OBJECT_CLASS_NAME("ProcessControlService")
|
||||
DECLARE_OBJECT_SINGLETON()
|
||||
|
||||
/**
|
||||
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
|
||||
* @param[in] ssObjectConfig Optional configuration string.
|
||||
*/
|
||||
void Initialize(const sdv::u8string& ssObjectConfig) override;
|
||||
|
||||
/**
|
||||
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
|
||||
* @return Return the current status of the object.
|
||||
*/
|
||||
sdv::EObjectStatus GetStatus() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
|
||||
* @param[in] eMode The operation mode, the component should run in.
|
||||
*/
|
||||
void SetOperationMode(sdv::EOperationMode eMode) override;
|
||||
|
||||
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
|
||||
// cppcheck-suppress virtualCallInConstructor
|
||||
/**
|
||||
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
|
||||
*/
|
||||
void Shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief Check for the application context mode being main or standalone.
|
||||
* @return Returns 'true' when the process control is allowed; otherwise returns 'false'.
|
||||
*/
|
||||
bool AllowProcessControl() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the process ID of that currently in. Overload of sdv::process::IProcessInfo::GetProcessID.
|
||||
* @return Return the process ID.
|
||||
*/
|
||||
sdv::process::TProcessID GetProcessID() const override;
|
||||
|
||||
/**
|
||||
* @brief Register a process lifetime monitor. Overload of sdv::process::IProcessLifetime::RegisterMonitor.
|
||||
* @param[in] tProcessID Process ID to monitor the lifetime for.
|
||||
* @param[in] pMonitor Pointer to the monitor interface. The monitor should expose the IProcessLifetimeCallback
|
||||
* interface.
|
||||
* @return Returns a non-zero cookie when successful; zero when not.
|
||||
*/
|
||||
virtual uint32_t RegisterMonitor(/*in*/ sdv::process::TProcessID tProcessID, /*in*/ sdv::IInterfaceAccess* pMonitor) override;
|
||||
|
||||
/**
|
||||
* @brief Unregistered a previously registered monitor. Overload of sdv::process::IProcessLifetime::UnregisterMonitor.
|
||||
* @param[in] uiCookie The cookie from the monitor registration.
|
||||
*/
|
||||
virtual void UnregisterMonitor(/*in*/ uint32_t uiCookie) override;
|
||||
|
||||
/**
|
||||
* @brief Wait for a process to finalize. Overload of sdv::process::IProcessLifetime::WaitForTerminate.
|
||||
* @param[in] tProcessID The process ID to wait for.
|
||||
* @param[in] uiWaitMs Maximum time to wait in ms. Could be 0xffffffff to wait indefintely.
|
||||
* @return Returns 'true' when the process was terminated (or isn't running), 'false' when still running and a timeout
|
||||
* has occurred.
|
||||
*/
|
||||
virtual bool WaitForTerminate(/*in*/ sdv::process::TProcessID tProcessID, /*in*/ uint32_t uiWaitMs) override;
|
||||
|
||||
/**
|
||||
* @brief Execute a process. Overload of sdv::process::IProcessControl::Execute.
|
||||
* @param[in] ssModule Module name of the process executable.
|
||||
* @param[in] seqArgs Instantiation arguments to supply to the process.
|
||||
* @param[in] eRights The process rights during instantiation.
|
||||
* @return Returns the process ID or 0 when process creation failed.
|
||||
*/
|
||||
virtual sdv::process::TProcessID Execute(/*in*/ const sdv::u8string& ssModule,
|
||||
/*in*/ const sdv::sequence<sdv::u8string>& seqArgs, /*in*/ sdv::process::EProcessRights eRights) override;
|
||||
|
||||
/**
|
||||
* @brief Terminate the process. Overload of sdv::process::IProcessControl::Terminate.
|
||||
* @attention Use this function as a last resort only. The process will be killed and anything unsaved will render invalid.
|
||||
* @param[in] tProcessID The process ID of the process to terminate.
|
||||
* @return Returns 'true' if termination was successful; returns 'false' if termination was not possible or not allowed.
|
||||
*/
|
||||
virtual bool Terminate(/*in*/ sdv::process::TProcessID tProcessID) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Monitor thread function.
|
||||
*/
|
||||
void MonitorThread();
|
||||
|
||||
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status.
|
||||
|
||||
std::mutex m_mtxProcessThreadShutdown; ///< Synchronize access
|
||||
#ifdef _WIN32
|
||||
std::map<sdv::process::TProcessID, std::pair<HANDLE,HANDLE>> m_mapProcessThreadShutdown; ///< Map with process IDs and event handles
|
||||
#elif __unix__
|
||||
std::set<sdv::process::TProcessID> m_setProcessThreadShutdown; ///< Set with process IDs
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Process helper structure
|
||||
*/
|
||||
struct SProcessHelper
|
||||
{
|
||||
sdv::process::TProcessID tProcessID = 0; ///< Process ID
|
||||
#ifdef _WIN32
|
||||
HANDLE hProcess = 0; ///< process handle
|
||||
#elif defined __unix__
|
||||
bool bNotAChild = false; ///< When set, the process is not a child of the monitor process.
|
||||
#else
|
||||
#error OS is not supported!
|
||||
#endif
|
||||
bool bRunning = true; ///< Set when the process is running and not terminated yet.
|
||||
int64_t iRetVal = 0; ///< Process return value.
|
||||
std::map<uint32_t, sdv::process::IProcessLifetimeCallback*> mapAssociatedMonitors; ///< Map with associated monitors.
|
||||
std::mutex mtxProcess; ///< Mutex for process access.
|
||||
std::condition_variable cvWaitForProcess; ///< Condition variable to wait for process termination.
|
||||
};
|
||||
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
|
||||
std::map<uint32_t, std::shared_ptr<SProcessHelper>> m_mapMonitors; ///< Map with monitors.
|
||||
bool m_bShutdown = false; ///< Set to shutdown the monitor thread.
|
||||
std::thread m_threadMonitor; ///< Monitor thread.
|
||||
};
|
||||
DEFINE_SDV_OBJECT(CProcessControl)
|
||||
|
||||
#endif // !define PROCESS_CONTROL_H
|
||||
46
sdv_services/proxy_stub/CMakeLists.txt
Normal file
46
sdv_services/proxy_stub/CMakeLists.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
# Include cross-compilation toolchain file
|
||||
include(../../cross-compile-tools.cmake)
|
||||
|
||||
# Use new policy for project version settings and default warning level
|
||||
cmake_policy(SET CMP0048 NEW) # requires CMake 3.14
|
||||
cmake_policy(SET CMP0092 NEW) # requires CMake 3.15
|
||||
|
||||
# Define project
|
||||
project(core_prox_stub VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Use C++17 support
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
# Libary symbols are hidden by default
|
||||
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
||||
|
||||
# Include link to export directory of "sdv-System". Replace this with link to installed location.
|
||||
include_directories(../../export)
|
||||
|
||||
# Include cross-compilation toolchain file
|
||||
include(../../cross-compile-tools.cmake)
|
||||
|
||||
# BIG Object for GCC on MINGW
|
||||
if (WIN32)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
add_compile_options(/bigobj)
|
||||
else()
|
||||
add_compile_options(-Wa,-mbig-obj)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Add the dynamic library
|
||||
file(MAKE_DIRECTORY "../../export/interfaces/ps/")
|
||||
file(TOUCH "../../export/interfaces/ps/proxystub.cpp")
|
||||
add_library(core_ps SHARED "../../export/interfaces/ps/proxystub.cpp" )
|
||||
target_link_options(core_ps PRIVATE)
|
||||
target_link_libraries(core_ps ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
# Set extension to .sdv
|
||||
set_target_properties(core_ps PROPERTIES PREFIX "")
|
||||
set_target_properties(core_ps PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
add_dependencies(core_ps CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} core_ps PARENT_SCOPE)
|
||||
42
sdv_services/task_timer/CMakeLists.txt
Normal file
42
sdv_services/task_timer/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
# Define project
|
||||
project(task_timer VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(task_timer SHARED "tasktimer.h" "tasktimer.cpp")
|
||||
|
||||
target_link_options(task_timer PRIVATE)
|
||||
if (WIN32)
|
||||
target_link_libraries(task_timer ${CMAKE_THREAD_LIBS_INIT} Winmm.lib)
|
||||
elseif (UNIX)
|
||||
target_link_libraries(task_timer ${CMAKE_THREAD_LIBS_INIT} rt)
|
||||
endif()
|
||||
set_target_properties(task_timer PROPERTIES PREFIX "")
|
||||
set_target_properties(task_timer PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(task_timer CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} task_timer PARENT_SCOPE)
|
||||
|
||||
|
||||
# Define project
|
||||
project(simulation_task_timer VERSION 1.0 LANGUAGES CXX)
|
||||
|
||||
# Define target
|
||||
add_library(simulation_task_timer SHARED "simulationtasktimer.h" "simulationtasktimer.cpp")
|
||||
|
||||
target_link_options(simulation_task_timer PRIVATE)
|
||||
if (WIN32)
|
||||
target_link_libraries(simulation_task_timer ${CMAKE_THREAD_LIBS_INIT} Winmm.lib)
|
||||
elseif (UNIX)
|
||||
target_link_libraries(simulation_task_timer ${CMAKE_THREAD_LIBS_INIT} rt)
|
||||
endif()
|
||||
set_target_properties(simulation_task_timer PROPERTIES PREFIX "")
|
||||
set_target_properties(simulation_task_timer PROPERTIES SUFFIX ".sdv")
|
||||
|
||||
# Build dependencies
|
||||
add_dependencies(simulation_task_timer CompileCoreIDL)
|
||||
|
||||
# Appending the service in the service list
|
||||
set(SDV_Service_List ${SDV_Service_List} task_timer simulation_task_timer PARENT_SCOPE)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user