Precommit (#1)

* first commit

* cleanup
This commit is contained in:
tompzf
2025-11-04 13:28:06 +01:00
committed by GitHub
parent dba45dc636
commit 6ed4b1534e
898 changed files with 256340 additions and 0 deletions

199
sdv_services/CMakeLists.txt Normal file
View 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)

View 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

View 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));
}

View 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

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

View 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);
}

View 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

View 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()

View 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);
}
}
}
}
}

View 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

View File

@@ -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"

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

View 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

View 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

View 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

View 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:
/// # &lt;exe_path&gt;/&lt;instance&gt;/&lt;installation&gt;
/// 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
/// # &lt;install_directory&gt;/&lt;instance&gt;/&lt;installation&gt;
/// # 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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;
}

View 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

View 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

View 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;
}

View 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

View 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
View 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

View 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;
}

View 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

View 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);
}

View 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

View 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
View 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

View 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

View 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

View 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;
}

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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
}

View 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

View 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;
}

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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);
}

View 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

View 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;
}

View 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

View 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

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

View 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);
}

View 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

View 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;
}

View 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

View 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;
}

View 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

View 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();
}

View 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

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

View 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
}

View 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

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

View 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;
}

View 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

View 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);
}

View 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

View 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);
}
}

View 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

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

View 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);
}

View 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

View 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;
}

View 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

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

View 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;
}

View 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

File diff suppressed because it is too large Load Diff

View 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

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

View 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));
}

View 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

View 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

View 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

View 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
}
}

View 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

View 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()

View 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;
}

View 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

View 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);
}
}
}

View 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

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

View 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;
}

View 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

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

View 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));
}
}

View 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

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

View 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