Update sdv_packager (#6)

This commit is contained in:
tompzf
2026-03-27 14:12:49 +01:00
committed by GitHub
parent 234be8917f
commit aefefd52f7
717 changed files with 42252 additions and 11334 deletions

View File

@@ -1,3 +1,16 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Erik Verhoeven - initial API and implementation
#*******************************************************************************
# Include cross-compilation toolchain file
include(../cross-compile-tools.cmake)
@@ -141,6 +154,14 @@ add_custom_command(
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/process.idl -O${INTERFACE_DIR} --no_ps
VERBATIM
)
add_custom_command(
OUTPUT ${INTERFACE_DIR}/param.h
DEPENDS sdv_idl_compiler
MAIN_DEPENDENCY ${INTERFACE_DIR}/param.idl
COMMENT "Compiling param.idl"
COMMAND sdv_idl_compiler ${INTERFACE_DIR}/param.idl -O${INTERFACE_DIR} --no_ps
VERBATIM
)
add_custom_command(
OUTPUT ${INTERFACE_DIR}/repository.h
DEPENDS sdv_idl_compiler
@@ -175,6 +196,7 @@ add_custom_target(CompileCoreIDL
${INTERFACE_DIR}/mem.h
${INTERFACE_DIR}/module.h
${INTERFACE_DIR}/process.h
${INTERFACE_DIR}/param.h
${INTERFACE_DIR}/repository.h
${INTERFACE_DIR}/timer.h
)
@@ -190,7 +212,8 @@ add_subdirectory(task_timer)
add_subdirectory(ipc_com)
add_subdirectory(ipc_connect)
add_subdirectory(ipc_shared_mem)
add_subdirectory(ipc_sockets)
add_subdirectory(uds_win_sockets)
add_subdirectory(uds_unix_sockets)
add_subdirectory(process_control)
add_subdirectory(hardware_ident)
add_subdirectory(manifest_util)

View File

@@ -1,5 +1,18 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Sudipta Durjoy - initial API and implementation
#*******************************************************************************
# 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"))
if((WIN32 AND MSVC) OR (UNIX AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64"))
# Define project
project(can_com_silkit VERSION 1.0 LANGUAGES CXX)

View File

@@ -1,80 +1,47 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Sudipta Durjoy - initial API and implementation
* Thomas Pfleiderer - refactored and finalized
********************************************************************************/
#include "can_com_silkit.h"
#include <support/toml.h>
#include <bitset>
void CCANSilKit::Initialize(const sdv::u8string& rssObjectConfig)
bool CCANSilKit::OnInitialize()
{
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))
if (!ValidateConfiguration())
{
m_eStatus = sdv::EObjectStatus::initialization_failure;
return;
return false;
}
if (!CreateSilKitConnection(silKitJSONConfigContent, silKitNetwork, silKitRegistryUri))
if (!CreateSilKitConnection())
{
SDV_LOG_ERROR("Error createing SilKit connection, probably invalid JSON content");
m_eStatus = sdv::EObjectStatus::initialization_failure;
return;
return false;
}
// Update status only if initialization was successful
m_eStatus = sdv::EObjectStatus::initialized;
return true;
}
bool CCANSilKit::ValidateConfiguration(const std::string& ssSilKitJSONConfigContent, const std::string& ssSilKitNetwork, const std::string& ssSilKitRegistryUri)
bool CCANSilKit::ValidateConfiguration()
{
bool success = true;
SDV_LOG_INFO("SilKit connecting to network: ", ssSilKitNetwork.c_str());
SDV_LOG_INFO("SilKit registry URI: ", ssSilKitRegistryUri.c_str());
SDV_LOG_INFO("SilKit connecting to network: ", m_SilKitNetwork.c_str());
SDV_LOG_INFO("SilKit registry URI: ", m_SilKitRegistryUri.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())
if (m_SilKitJSONConfigContent.empty())
{
SDV_LOG_ERROR("Error reading config file content for SilKit, missing 'SilKitConfig'.");
success = false;
@@ -84,23 +51,23 @@ bool CCANSilKit::ValidateConfiguration(const std::string& ssSilKitJSONConfigCont
SDV_LOG_ERROR("SilKit CAN participant is not found in configuration, missing 'SilKitParticipantName'.");
success = false;
}
if (ssSilKitNetwork.empty())
if (m_SilKitNetwork.empty())
{
SDV_LOG_ERROR("Error reading SilKit network name, missing 'CanSilKitNetwork'.");
success = false;
}
else
{
SDV_LOG_INFO("SilKit connecting to network: ", ssSilKitNetwork.c_str());
SDV_LOG_INFO("SilKit connecting to network: ", m_SilKitNetwork.c_str());
}
if (ssSilKitRegistryUri.empty())
if (m_SilKitRegistryUri.empty())
{
SDV_LOG_ERROR("Error reading SilKit registry URI, missing 'RegistryURI'.");
success = false;
}
else
{
SDV_LOG_INFO("SilKit registry URI: ", ssSilKitRegistryUri.c_str());
SDV_LOG_INFO("SilKit registry URI: ", m_SilKitRegistryUri.c_str());
}
if (!m_TimerSimulationStep)
@@ -120,68 +87,41 @@ bool CCANSilKit::ValidateConfiguration(const std::string& ssSilKitJSONConfigCont
return success;
}
sdv::EObjectStatus CCANSilKit::GetStatus() const
void CCANSilKit::OnShutdown()
{
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;
}
m_SilKitLifeCycleService = nullptr;
m_SilKitCanController = nullptr;
m_SilKitParticipant = nullptr;
std::lock_guard<std::mutex> lock(m_QueueMutex);
while (!m_MessageQueue.empty())
{
@@ -191,12 +131,13 @@ void CCANSilKit::Shutdown()
void CCANSilKit::RegisterReceiver(/*in*/ sdv::can::IReceive* pReceiver)
{
if (m_eStatus != sdv::EObjectStatus::configuring)
if (GetObjectState() != sdv::EObjectState::configuring)
return;
if (!pReceiver)
{
SDV_LOG_ERROR("No CAN receiver available.");
SetObjectIntoConfigErrorState();
return;
}
@@ -234,7 +175,7 @@ void CCANSilKit::UnregisterReceiver(/*in*/ sdv::can::IReceive* pReceiver)
sdv::sequence<sdv::u8string> CCANSilKit::GetInterfaces() const
{
sdv::sequence<sdv::u8string> seqIfcNames;
if (m_eStatus != sdv::EObjectStatus::running) return seqIfcNames;
if (GetObjectState() != sdv::EObjectState::running) return seqIfcNames;
seqIfcNames.push_back(m_SilKitParticipantName);
@@ -249,6 +190,7 @@ std::shared_ptr<SilKit::Config::IParticipantConfiguration> CCANSilKit::GetSilKit
if (silKitParticipantCconfig == nullptr)
{
SDV_LOG_ERROR("Error parsing the SilKit config content: ", ssSilKitJSONConfigContent.c_str());
SetObjectIntoConfigErrorState();
return nullptr;
}
@@ -261,6 +203,7 @@ std::unique_ptr<SilKit::IParticipant> CCANSilKit::CreateParticipantFromJSONConfi
if (silKitParticipantConfig == nullptr)
{
SDV_LOG_ERROR("The SilKit configuaration file could not be opened.");
SetObjectIntoConfigErrorState();
return nullptr;
}
@@ -322,23 +265,24 @@ bool CCANSilKit::SetHandlerFunctions(SilKit::Services::Orchestration::ILifecycle
try
{
// Set a SilKit Stop Handler
silKitLifeCycleService->SetStopHandler([this]()
silKitLifeCycleService->SetStopHandler(
[this]()
{
m_eStatus = sdv::EObjectStatus::runtime_error;
SetObjectIntoRuntimeErrorState();
SDV_LOG_INFO("SilKit StopHandlerhandler called");
});
// Set a Shutdown Handler
silKitLifeCycleService->SetShutdownHandler([this]()
{
m_eStatus = sdv::EObjectStatus::runtime_error;
SetObjectIntoRuntimeErrorState();
SDV_LOG_INFO("SilKit Shutdown handler called");
});
// Set a Shutdown Handler
silKitLifeCycleService->SetAbortHandler([this](auto /*participantState*/)
{
m_eStatus = sdv::EObjectStatus::runtime_error;
SetObjectIntoRuntimeErrorState();
SDV_LOG_INFO("SilKit Abort handler called");
});
@@ -363,18 +307,18 @@ bool CCANSilKit::SetHandlerFunctions(SilKit::Services::Orchestration::ILifecycle
return true;
}
bool CCANSilKit::CreateSilKitConnection(const std::string& ssSilKitJSONConfigContent, const std::string& ssSilKitNetwork, const std::string& ssSilKitRegistryUri)
bool CCANSilKit::CreateSilKitConnection()
{
try
{
m_SilKitParticipant = CreateParticipantFromJSONConfig(ssSilKitJSONConfigContent, ssSilKitRegistryUri);
m_SilKitParticipant = CreateParticipantFromJSONConfig(m_SilKitJSONConfigContent, m_SilKitRegistryUri);
if (m_SilKitParticipant == nullptr)
{
SDV_LOG_ERROR("SilKit COM adapter is not available.");
return false;
}
m_SilKitCanController = CreateController(ssSilKitNetwork);
m_SilKitCanController = CreateController(m_SilKitNetwork);
if (m_SilKitCanController == nullptr)
{
SDV_LOG_ERROR("SilKit CAN controller is not available.");
@@ -416,7 +360,7 @@ bool CCANSilKit::CreateSilKitConnection(const std::string& ssSilKitJSONConfigCon
void CCANSilKit::SilKitReceiveMessageHandler(const SilKit::Services::Can::CanFrame& rsSilKitCanFrame)
{
if (m_eStatus != sdv::EObjectStatus::running)
if (GetObjectState() != sdv::EObjectState::running)
return;
if (rsSilKitCanFrame.dlc > m_maxCanDataLength)
@@ -461,7 +405,7 @@ void CCANSilKit::SilKitTransmitAcknowledgeHandler(const SilKit::Services::Can::C
void CCANSilKit::Send(/*in*/ const sdv::can::SMessage& sSDVCanMessage, /*in*/ uint32_t)
{
if (m_eStatus != sdv::EObjectStatus::running)
if (GetObjectState() != sdv::EObjectState::running)
return;
if(sSDVCanMessage.bCanFd)

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Sudipta Durjoy - initial API and implementation
* Thomas Pfleiderer - refactored and finalized
********************************************************************************/
#ifndef CAN_COM_SILKIT_H
#define CAN_COM_SILKIT_H
@@ -18,46 +32,54 @@
/**
* @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
class CCANSilKit : public sdv::CSdvObject, 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_TYPE(sdv::EObjectType::vehicle_bus)
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;
BEGIN_SDV_PARAM_MAP()
SDV_PARAM_ENABLE_LOCKING()
SDV_PARAM_ENTRY(m_SilKitJSONConfigContent, "SilKitConfig", "", "", "SilKit Config JSON.")
SDV_PARAM_ENTRY(m_SilKitParticipantName, "SilKitParticipantName", "", "", "Name of the participant.")
SDV_PARAM_ENTRY(m_SilKitNetwork, "CanSilKitNetwork", "", "", "Declaration of SilKit CAN network.")
SDV_PARAM_ENTRY(m_SilKitRegistryUri, "RegistryURI", "", "", "SilKit Registry URI.")
SDV_PARAM_ENTRY(m_SilKitIsSynchronousMode, "SyncMode", "", "", "Enable synchronization mode.")
SDV_PARAM_ENTRY(m_SilKitDebugInfo, "DebugInfo", "", "", "Enable debug information.")
END_SDV_PARAM_MAP()
/**
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
* @return Return the current status of the object.
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
* @details The CAN_Com_SilKit uses the following configuration:
* @code
* DebugInfo = true
* SyncMode = true
* SilKitParticipantName = "can_writer"
* CanSilKitNetwork = "PrivateCAN"
* RegistryURI = "silkit://localhost:8500"
* SilKitConfig = """{
* "Logging": {
* "Sinks": [ { "Type": "Stdout", "Level": "Info" } ]
* },
* }"""
* @endcode
* @return Returns 'true' when the initialization was successful, 'false' when not.
*/
virtual sdv::EObjectStatus GetStatus() const override;
virtual bool OnInitialize() override;
/**
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
* @param[in] eMode The operation mode, the component should run in.
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
*/
void SetOperationMode(sdv::EOperationMode eMode) override;
/**
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
*/
virtual void Shutdown() override;
virtual void OnShutdown() override;
/**
* @brief Register a CAN message receiver. Overload of sdv::can::IRegisterReceiver::RegisterReceiver.
@@ -136,16 +158,13 @@ private:
* @param[in] ssSilKitNetwork SilKit network.
* @return SilKit::Services::Can::ICanController, nullptr on failure.
*/
SilKit::Services::Can::ICanController* CreateController(const std::string& ssSilKitNetwork);
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);
bool ValidateConfiguration();
/**
* @brief Function for SilKit Timesyncservice creation and to set simulation step handler.
@@ -154,12 +173,9 @@ private:
/**
* @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);
bool CreateSilKitConnection();
/**
* @brief Create lifecycle service.
@@ -185,21 +201,24 @@ private:
*/
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::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.
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.
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.
bool m_SilKitDebugInfo = false; ///< SilKit debug information 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.
uint32_t m_maxCanDataLength = 8; ///< maximum size of the CAN message.
std::string m_SilKitNetwork; ///< Declaration of SilKit CAN network.
std::string m_SilKitJSONConfigContent; ///< SilKit config JSON.
std::string m_SilKitRegistryUri;
};
DEFINE_SDV_OBJECT(CCANSilKit)

View File

@@ -1,3 +1,16 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Erik Verhoeven - initial API and implementation
#*******************************************************************************
# Define project
project(can_com_sim VERSION 1.0 LANGUAGES CXX)

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "can_com_sim.h"
#include <support/toml.h>
#include "../../global/ascformat/ascreader.cpp"
@@ -9,51 +22,30 @@ CCANSimulation::CCANSimulation()
CCANSimulation::~CCANSimulation()
{}
void CCANSimulation::Initialize(const sdv::u8string& rssObjectConfig)
bool CCANSimulation::OnInitialize()
{
try
if (m_pathSource.empty() && m_pathTarget.empty())
{
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;
}
SDV_LOG(sdv::core::ELogSeverity::error,
"At least the source or the target ASC files must be specified.");
return false;
}
catch (const sdv::toml::XTOMLParseException& e)
else if (m_pathSource == m_pathTarget)
{
SDV_LOG(sdv::core::ELogSeverity::error, "Configuration could not be read: ", e.what());
m_eStatus = sdv::EObjectStatus::initialization_failure;
SDV_LOG(sdv::core::ELogSeverity::error,
"Source and target ASC files '" + m_pathSource.generic_u8string() + "' cannot be the same.");
return false;
}
catch (const std::runtime_error& e)
else if (std::filesystem::exists(m_pathTarget))
{
SDV_LOG(sdv::core::ELogSeverity::error, "Configuration could not be read: ", e.what());
m_eStatus = sdv::EObjectStatus::initialization_failure;
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.");
return false;
}
if (m_eStatus == sdv::EObjectStatus::initialization_failure) return;
// Initialize the ASC writer
if (!m_pathTarget.empty())
@@ -69,60 +61,36 @@ void CCANSimulation::Initialize(const sdv::u8string& rssObjectConfig)
{
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;
return false;
}
if (!m_pathSource.empty() && !m_reader.GetMessageCount())
{
SDV_LOG(sdv::core::ELogSeverity::error,
"No messages in ASC file '" + m_pathSource.generic_u8string() + "' found. File must contain 'Begin TriggerBlock' and 'End TriggerBlock' line.");
m_eStatus = sdv::EObjectStatus::initialization_failure;
return;
return false;
}
if (!m_pathSource.empty())
SDV_LOG(sdv::core::ELogSeverity::info,
"CAN simulator ASC file '" + m_pathSource.generic_u8string() + "' contains ", m_reader.GetMessageCount(), " messages.");
// Update the status
m_eStatus = sdv::EObjectStatus::initialized;
return true;
}
sdv::EObjectStatus CCANSimulation::GetStatus() const
void CCANSimulation::OnChangeToConfigMode()
{
return m_eStatus;
// Stop playback
m_reader.StopPlayback();
}
void CCANSimulation::SetOperationMode(sdv::EOperationMode eMode)
bool CCANSimulation::OnChangeToRunningMode()
{
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;
}
// Start playback
m_reader.StartPlayback([&](const asc::SCanMessage& rsMsg) { PlaybackFunc(rsMsg); });
return true;
}
void CCANSimulation::Shutdown()
void CCANSimulation::OnShutdown()
{
m_eStatus = sdv::EObjectStatus::shutdown_in_progress;
// Stop playback
m_reader.StopPlayback();
@@ -133,14 +101,11 @@ void CCANSimulation::Shutdown()
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 (GetObjectState() != sdv::EObjectState::configuring) return;
if (!pReceiver) return;
std::unique_lock<std::mutex> lock(m_mtxReceivers);
@@ -161,7 +126,7 @@ void CCANSimulation::UnregisterReceiver(/*in*/ sdv::can::IReceive* pReceiver)
void CCANSimulation::Send(/*in*/ const sdv::can::SMessage& sMsg, /*in*/ uint32_t uiIfcIndex)
{
if (m_eStatus != sdv::EObjectStatus::running) return;
if (GetObjectState() != sdv::EObjectState::running) return;
asc::SCanMessage sAscCan{};
sAscCan.uiChannel = uiIfcIndex + 1;
@@ -177,7 +142,7 @@ void CCANSimulation::Send(/*in*/ const sdv::can::SMessage& sMsg, /*in*/ uint32_t
sdv::sequence<sdv::u8string> CCANSimulation::GetInterfaces() const
{
sdv::sequence<sdv::u8string> seqIfcNames;
if (m_eStatus != sdv::EObjectStatus::running) return seqIfcNames;
if (GetObjectState() != sdv::EObjectState::running) return seqIfcNames;
std::unique_lock<std::mutex> lock(m_mtxInterfaces);
for (const auto& rprInterface : m_vecInterfaces)
seqIfcNames.push_back(rprInterface.second);
@@ -186,7 +151,7 @@ sdv::sequence<sdv::u8string> CCANSimulation::GetInterfaces() const
void CCANSimulation::PlaybackFunc(const asc::SCanMessage& rsMsg)
{
if (m_eStatus != sdv::EObjectStatus::running) return;
if (GetObjectState() != sdv::EObjectState::running) return;
// Create sdv CAN message
sdv::can::SMessage sSdvCan{};

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef CAN_COM_SIMULATION_H
#define CAN_COM_SIMULATION_H
@@ -15,8 +28,7 @@
/**
* @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
class CCANSimulation : public sdv::CSdvObject, public sdv::can::IRegisterReceiver, public sdv::can::ISend, sdv::can::IInformation
{
public:
/**
@@ -31,39 +43,47 @@ 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)
// Declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::vehicle_bus)
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;
// Parameter map
BEGIN_SDV_PARAM_MAP()
SDV_PARAM_PATH_ENTRY(m_pathSource, "Source", "", "Path to the source ASC file.")
SDV_PARAM_PATH_ENTRY(m_pathTarget, "Target", "", "Path to the target ASC file.")
END_SDV_PARAM_MAP()
/**
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
* @return Return the current status of the object.
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
* @return Returns 'true' when the initialization was successful, 'false' when not.
*/
virtual sdv::EObjectStatus GetStatus() const override;
virtual bool OnInitialize() override;
/**
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
* @param[in] eMode The operation mode, the component should run in.
* @brief Change to configuration mode event. After this a call to this function locked parameters can be changed again.
* Overload of sdv::CSdvObject::OnChangeToConfigMode.
*/
void SetOperationMode(sdv::EOperationMode eMode) override;
virtual void OnChangeToConfigMode() override;
/**
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
* @brief Change to running mode event. Parameters were locked before the call to this event. Overload of
* sdv::CSdvObject::OnChangeToRunningMode.
* @return Returns 'true' when the configuration is valid and the running instances could be started. Otherwise returns
* 'false'.
*/
virtual void Shutdown() override;
virtual bool OnChangeToRunningMode() override;
/**
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
*/
virtual void OnShutdown() override;
/**
* @brief Register a CAN message receiver. Overload of sdv::can::IRegisterReceiver::RegisterReceiver.
@@ -98,7 +118,6 @@ private:
*/
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.

View File

@@ -1,3 +1,16 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Sudipta Durjoy - initial API and implementation
#*******************************************************************************
# Build for linux only
if(UNIX)

View File

@@ -1,11 +1,25 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Sudipta Durjoy - initial API and implementation
* Thomas Pfleiderer - refactored and finalized
********************************************************************************/
#include "can_com_sockets.h"
void CCANSockets::Initialize(const sdv::u8string& rssObjectConfig)
bool CCANSockets::OnInitialize()
{
std::deque<std::string> vecConfigInterfaces;
try
{
sdv::toml::CTOMLParser config(rssObjectConfig.c_str());
sdv::toml::CTOMLParser config(GetObjectConfig());
sdv::toml::CNodeCollection nodeSource = config.GetDirect("canSockets");
if (nodeSource.GetType() == sdv::toml::ENodeType::node_array)
{
@@ -35,8 +49,7 @@ void CCANSockets::Initialize(const sdv::u8string& rssObjectConfig)
catch (const sdv::toml::XTOMLParseException& e)
{
SDV_LOG_ERROR("Configuration could not be read: ", e.what());
m_eStatus = sdv::EObjectStatus::initialization_failure;
return;
return false;
}
if (vecConfigInterfaces.size() == 0)
@@ -46,13 +59,40 @@ void CCANSockets::Initialize(const sdv::u8string& rssObjectConfig)
if (!SetupCANSockets(vecConfigInterfaces))
{
m_eStatus = sdv::EObjectStatus::initialization_failure;
return;
LogAllCanInterfaceNames();
return false;
}
LogConfigurations();
m_threadReceive = std::thread(&CCANSockets::ReceiveThreadFunc, this);
m_eStatus = sdv::EObjectStatus::initialized;
return true;
}
void CCANSockets::LogConfigurations()
{
for (const auto& socket : m_vecSockets)
{
if ((socket.localSocket > 0) && (socket.networkInterface > 0))
{
SDV_LOG_INFO("Configured socket: ", socket.name, ", index: ", socket.networkInterface);
}
}
}
void CCANSockets::LogAllCanInterfaceNames()
{
// Retrieve the list of available can interfaces
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) != -1)
{
SDV_LOG_INFO("List of available can socket interfaces:");
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
SDV_LOG_INFO(ifa->ifa_name);
}
freeifaddrs(ifaddr);
}
}
bool CCANSockets::SetupCANSockets(const std::deque<std::string>& vecConfigInterfaces)
@@ -60,13 +100,16 @@ bool CCANSockets::SetupCANSockets(const std::deque<std::string>& vecConfigInterf
// Retrieve the list of available interfaces
std::set<std::string> availableInterfaces;
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) != -1)
if (getifaddrs(&ifaddr) == -1)
{
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
availableInterfaces.insert(ifa->ifa_name);
}
SDV_LOG_ERROR(" Error: getifaddrs()");
return false;
}
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
availableInterfaces.insert(ifa->ifa_name);
}
freeifaddrs(ifaddr);
CreateAndBindSockets(vecConfigInterfaces, availableInterfaces);
@@ -152,32 +195,8 @@ void CCANSockets::CreateAndBindSockets(const std::deque<std::string>& vecConfigI
}
}
sdv::EObjectStatus CCANSockets::GetStatus() const
void CCANSockets::OnShutdown()
{
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();
@@ -190,13 +209,11 @@ void CCANSockets::Shutdown()
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 (GetObjectState() != sdv::EObjectState::configuring) return;
if (!pReceiver) return;
SDV_LOG_INFO("Registering VAPI CAN communication receiver...");
@@ -222,7 +239,7 @@ void CCANSockets::UnregisterReceiver(/*in*/ sdv::can::IReceive* pReceiver)
sdv::sequence<sdv::u8string> CCANSockets::GetInterfaces() const
{
sdv::sequence<sdv::u8string> seqIfcNames;
if (m_eStatus != sdv::EObjectStatus::running)
if (GetObjectState() != sdv::EObjectState::running)
{
return seqIfcNames;
}
@@ -241,7 +258,7 @@ sdv::sequence<sdv::u8string> CCANSockets::GetInterfaces() const
void CCANSockets::Send(const sdv::can::SMessage& sMsg, uint32_t uiConfigIndex)
{
if (m_eStatus != sdv::EObjectStatus::running) return;
if (GetObjectState() != sdv::EObjectState::running) return;
if (sMsg.bCanFd) return; // CAN-FD not supported
if (sMsg.seqData.size() > 8) return; // Invalid message length.
@@ -267,15 +284,17 @@ void CCANSockets::Send(const sdv::can::SMessage& sMsg, uint32_t uiConfigIndex)
std::unique_lock<std::mutex> lock(m_mtxSockets);
auto it = m_vecSockets.begin();
std::advance(it, uiConfigIndex);
if (it != m_vecSockets.end())
for (const auto& socket : m_vecSockets)
{
if ((it->localSocket > 0) && (it->networkInterface > 0))
if ((socket.localSocket > 0) && (socket.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));
if ((uint)socket.networkInterface == uiConfigIndex)
{
sAddr.can_ifindex = socket.networkInterface;
sAddr.can_family = AF_CAN;
sendto(socket.localSocket, &sFrame, sizeof(can_frame), 0, reinterpret_cast<sockaddr*>(&sAddr), sizeof(sAddr));
break;
}
}
}
}
@@ -285,16 +304,16 @@ void CCANSockets::ReceiveThreadFunc()
while (true)
{
enum {retry, cont, exit} eNextStep = exit;
switch (m_eStatus)
switch (GetObjectState())
{
case sdv::EObjectStatus::configuring:
case sdv::EObjectStatus::initialization_pending:
case sdv::EObjectStatus::initialized:
case sdv::EObjectStatus::initializing:
case sdv::EObjectState::configuring:
case sdv::EObjectState::initialization_pending:
case sdv::EObjectState::initialized:
case sdv::EObjectState::initializing:
std::this_thread::sleep_for(std::chrono::milliseconds(10));
eNextStep = retry;
break;
case sdv::EObjectStatus::running:
case sdv::EObjectState::running:
eNextStep = cont;
break;
default:

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Sudipta Durjoy - initial API and implementation
* Thomas Pfleiderer - refactored and finalized
********************************************************************************/
#ifndef CAN_COM_SOCKET_H
#define CAN_COM_SOCKET_H
@@ -29,7 +43,7 @@
/**
* @brief Component to establish Socket CAN communication between VAPI and external application
*/
class CCANSockets : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::can::IRegisterReceiver,
class CCANSockets : public sdv::CSdvObject, public sdv::can::IRegisterReceiver,
public sdv::can::ISend, sdv::can::IInformation
{
public:
@@ -42,39 +56,27 @@ public:
SDV_INTERFACE_ENTRY(sdv::can::IInformation)
END_SDV_INTERFACE_MAP()
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::Device)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::vehicle_bus)
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.
* @brief Initialize the object. Overload of sdv::CSdvObject::OnInitialize.
* 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"]
* @return Returns 'true' when the initialization was successful, 'false' when not.
*/
virtual void Initialize(const sdv::u8string& ssObjectConfig) override;
virtual bool OnInitialize() override;
/**
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
* @return Return the current status of the object.
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
*/
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;
virtual void OnShutdown() override;
/**
* @brief Register a CAN message receiver. Overload of sdv::can::IRegisterReceiver::RegisterReceiver.
@@ -124,6 +126,16 @@ private:
void CreateAndBindSockets(const std::deque<std::string>& vecConfigInterfaces,
const std::set<std::string>& availableInterfaces);
/**
* @brief Write log information about the configured can sockets
*/
void LogConfigurations();
/**
* @brief Write log information about the existing can hardware
*/
void LogAllCanInterfaceNames();
/**
* @brief Socket definition structure
*/
@@ -134,7 +146,6 @@ private:
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.

View File

@@ -12,6 +12,7 @@ Class = "DataDispatchService"
[[Component]]
Path = "can_com_sockets.sdv"
Class = "CAN_Com_Sockets"
[Component.Parameters]
canSockets=["llcecan0"]
[[Component]]

View File

@@ -1,3 +1,16 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Erik Verhoeven - initial API and implementation
#*******************************************************************************
# Define project
project(sdv_services_core VERSION 1.0 LANGUAGES CXX)
@@ -42,7 +55,10 @@ add_library(core_services SHARED
"installation_composer.h"
"installation_composer.cpp"
"toml_parser/lexer_toml_token.h" "toml_parser/lexer_toml_token.cpp" "toml_parser/miscellaneous.h" "toml_parser/miscellaneous.cpp")
"toml_parser/lexer_toml_token.h" "toml_parser/lexer_toml_token.cpp" "toml_parser/miscellaneous.h" "toml_parser/miscellaneous.cpp" "toml_parser/code_snippet.h" "toml_parser/code_snippet.cpp" "app_settings.h" "app_settings.cpp" "app_config_file.h" "app_config_file.cpp")
# Compiler settiings
add_compile_definitions(SDV_NO_EXPORT_DEFINITION)
# Link target
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef APP_CONFIG_H
#define APP_CONFIG_H
@@ -9,6 +22,7 @@
#include <filesystem>
#include <vector>
#include <map>
#include "app_config_file.h"
// @cond DOXYGEN_IGNORE
// Components are installed using two procedures:
@@ -25,6 +39,12 @@
// The installation might need companion files to be installed in various relative sub-directories.
// @endcond
/**
* @brief Get the location of the core_services.sdv.
* @return Path to the directory containing the loaded core directory.
*/
std::filesystem::path GetCoreDirectory();
/**
* @brief Application configuration service
* @details In the configuration system objects, devices, basic services, complex services and apps are defined and will be started
@@ -33,33 +53,6 @@
* 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)
@@ -85,7 +78,10 @@ public:
END_SDV_INTERFACE_MAP()
/**
* @brief Load the installation manifests for core, executable and user components. Main and isolated applications only.
* @brief Load/reload the installation manifests for core, executable and user components.
* @details The installation manifests contain the module and component class information that is used for finding component
* classes. Standalone applications only load the core manifest. Server applications additionally load the manifest according to
* their instance ID.
* @return Returns 'true when successful; 'false' when not.
*/
bool LoadInstallationManifests();
@@ -95,6 +91,25 @@ public:
*/
void UnloadInstallatonManifests();
/**
* @brief Load the configuration files listed in the settings.
* @details Load the configuration files stored in the settings file (for server applications) or provided through the startup
* configuration (for local applications). For server applications, the system configurations are loaded and provided to the
* repository for starting the components. After the system configuration, the user configuration is loaded. The module section
* and path information in the configuration files is ignored (if available at all) for server applications. For local
* applications there are no system configurations to load and only the user configuration is loaded which was provided during
* startup. The loading of the modules precedes the loading and starting of the components.
* @return Returns 'true' when the loading was successful. 'False' when not.
*/
bool LoadAppConfigs();
/**
* @brief Save the configuration files listed in the settings.
* @remarks When running in maintenance mode, all configurations are updated. In all other modes, only the
* user application config is updated.
*/
void SaveAppConfigs();
/**
* @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
@@ -123,10 +138,24 @@ public:
* @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.
* @return Returns 'true' on success (or no changes detected); 'false' otherwise.
*/
virtual bool SaveConfig(/*in*/ const sdv::u8string& ssConfigPath) const override;
/**
* @brief Generate the configuration TOML string. Overload of sdv::core::IConfig::GenerateConfigString.
* @return The generated configuration string.
*/
virtual sdv::u8string GenerateConfigString() const override;
/**
* @brief Close the current configuration. Overload of sdv::core::IConfig::CloseConfig.
* @details This will close und unload the components and modules from the current configuration as well as dependent
* components that builds on top of the components being closed. Components that the current configuration depends on
* are not closed.
*/
virtual void CloseConfig() override;
/**
* @brief Add a search path to a folder where a config file can be found. Overload of
* sdv::core::IConfig::AddDirectorySearchDir.
@@ -136,7 +165,9 @@ public:
virtual bool AddConfigSearchDir(/*in*/ const sdv::u8string& ssDir) override;
/**
* @brief Reset the configuration baseline.
* @brief Reset the configuration baseline. Overload of sdv::core::IConfig::ResetConfigBaseline.
* @remarks When running as server, the config baseline is limited to the user configuration, which only consists of
* complex services. For standalone setups, the configuration can also contain basic services and devices.
* @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.
*/
@@ -209,17 +240,37 @@ public:
/**
* @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.
* @details Find the first component containing a class with the specified name. The order of checking the installation manifest
* is core-manifest, manifest in executable directory and manifest in supplied installation directory. For standalone
* applications only the core and executable manifests are checked.
* @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.
* @return Optionally returns the component class information.
*/
std::optional<CInstallManifest::SComponent> FindInstalledComponent(const std::string& rssClass) const;
std::optional<sdv::SClassInfo> FindInstalledComponent(const std::string& rssClass) const;
/**
* @brief Remove any components that are mentioned in the manifest from the configurations available in the settings file.
* @remarks Only available when running as maintenance or main application. If running as main application limited to removal
* from the user configuration only.
* @param[in] rManifest Reference to the manifest file containing the installation components.
* @return Returns whether the removal was successful (also when there was nothing to remove) or not successful (when the
* components were in one of the system configurations and the application is not running as maintenance application).
*/
bool RemoveFromConfig(const CInstallManifest& rManifest);
private:
/**
* @brief For standalone applications, compose a proper absolute configuration file path based a relative path and the search
* directories provided.
* @remarks The path could point to a non-existing file (in case of the save configuration call). The parent directory needs
* to exist.
* @param[in] rpathConfigFile Reference to the path containing a relatice or absolute path name to the configuration file.
* @return Returns a fully qualified path to the configuration file. Or an empty path in case of failure.
*/
std::filesystem::path ComposeConfigPathStandaloneApp(const std::filesystem::path& rpathConfigFile) const;
/**
* @brief Add config folders of the core_services and the executable to the search paths if not done so already.
*/
@@ -233,17 +284,23 @@ private:
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.
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.
std::vector<CAppConfigFile> m_vecSysConfigs; ///< System configurations.
CAppConfigFile m_configUserConfig; ///< User configuration file.
};
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
/**
* @brief Return the application config class.
* @return Reference to the application config class.
*/
CAppConfig& GetAppConfig();
/**
* @brief App config service class.
@@ -270,7 +327,7 @@ public:
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("ConfigService")
DECLARE_OBJECT_SINGLETON()
@@ -292,8 +349,6 @@ public:
*/
static bool EnableAppInstallAccess();
};
DEFINE_SDV_OBJECT_NO_EXPORT(CAppConfigService)
#endif
DEFINE_SDV_OBJECT(CAppConfigService)
#endif // !defined APP_CONFIG_H

View File

@@ -0,0 +1,816 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "app_config_file.h"
#include <support/toml.h>
#include "app_settings.h"
#include "../../global/ipc_named_mutex.h"
#include "toml_parser/parser_toml.h"
#include <support/toml.h>
#include "installation_manifest.h"
#include <set>
CAppConfigFile::CAppConfigFile(const std::filesystem::path& rpathConfigFile)
{
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::maintenance:
m_pathConfigFile = GetAppSettings().GetInstallDir() / rpathConfigFile;
break;
default:
m_pathConfigFile = rpathConfigFile;
break;
}
if (!LoadConfigFile())
{
SDV_LOG_ERROR("ERROR: failed to load the configuration file: ", rpathConfigFile.generic_u8string());
}
}
void CAppConfigFile::Clear()
{
m_pathConfigFile.clear();
m_bLoaded = false;
m_vecComponentList.clear();
m_vecClassList.clear();
m_vecModuleList.clear();
}
bool CAppConfigFile::IsLoaded() const
{
return m_bLoaded;
}
const std::filesystem::path& CAppConfigFile::ConfigPath() const
{
return m_pathConfigFile;
}
bool CAppConfigFile::LoadConfigFile()
{
if (!m_pathConfigFile.has_filename() || !std::filesystem::exists(m_pathConfigFile.parent_path()))
return false;
if (!std::filesystem::exists(m_pathConfigFile))
{
m_bLoaded = true;
return true;
}
std::ifstream fstream(m_pathConfigFile);
std::string ssConfig((std::istreambuf_iterator<char>(fstream)), std::istreambuf_iterator<char>());
fstream.close();
return LoadConfigFromString(ssConfig);
}
bool CAppConfigFile::LoadConfigFromString(const std::string& rssConfig)
{
// Determine whether running in main, isolation or maintenance mode.
bool bServerApp = false;
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::maintenance:
bServerApp = true;
break;
default:
break;
}
try
{
// Read the configuration
toml_parser::CParser parserConfig(rssConfig);
// Check for the version
sdv::toml::CNodeCollection nodeConfig(&parserConfig.Root());
uint32_t uiVersion = nodeConfig.GetDirect("Configuration.Version").GetValue();
if (uiVersion != SDVFrameworkInterfaceVersion)
{
SDV_LOG_ERROR("Invalid version of application settings file (expected version ",
SDVFrameworkInterfaceVersion,
", but available version ",
uiVersion,
")");
return false;
}
// Iterate through the module list (if not running as server application).
if (!bServerApp)
{
sdv::toml::CNodeCollection nodeModules = nodeConfig.GetDirect("Module");
for (size_t nIndex = 0; nIndex < nodeModules.GetCount(); nIndex++)
{
sdv::toml::CNodeCollection nodeModule = nodeModules.Get(nIndex);
SModule sModule;
sModule.pathModule = nodeModule.GetDirect("Path").GetValueAsPath();
auto itModule = std::find_if(m_vecModuleList.begin(), m_vecModuleList.end(),
[&](const SModule& rsModuleStored) { return rsModuleStored.pathModule == sModule.pathModule; });
if (itModule == m_vecModuleList.end())
m_vecModuleList.push_back(std::move(sModule));
}
}
// Iterate through the class list
sdv::toml::CNodeCollection arrayClasses = nodeConfig.GetDirect("Class");
for (size_t nIndex = 0; nIndex < arrayClasses.GetCount(); nIndex++)
{
sdv::toml::CNodeCollection tableClass = arrayClasses.Get(nIndex);
sdv::SClassInfo sClass = TOML2ClassInfo(parserConfig.Root().Cast<toml_parser::CNodeCollection>(), nIndex);
if (sClass.ssName.empty() || sClass.eType == sdv::EObjectType::undefined) continue;
auto itClass = std::find_if(m_vecClassList.begin(), m_vecClassList.end(),
[&](const sdv::SClassInfo& rsClassStored) { return rsClassStored.ssName == sClass.ssName; });
if (itClass == m_vecClassList.end())
m_vecClassList.push_back(std::move(sClass));
}
// Iterate through the component list
sdv::toml::CNodeCollection nodeComponents = nodeConfig.GetDirect("Component");
for (size_t nIndex = 0; nIndex < nodeComponents.GetCount(); nIndex++)
{
sdv::toml::CNodeCollection tableComponent = nodeComponents.Get(nIndex);
SComponent sComponent;
if (!bServerApp)
sComponent.pathModule = tableComponent.GetDirect("Path").GetValue().get<std::string>();
sComponent.ssClassName = tableComponent.GetDirect("Class").GetValue().get<std::string>();
if (sComponent.ssClassName.empty())
{
SDV_LOG_ERROR("Missing class name for the class definition in the configuration file '", m_pathConfigFile, "'.");
return false;
}
sComponent.ssInstanceName = tableComponent.GetDirect("Name").GetValue().get<std::string>();
// NOTE: The name could be empty. The system will automatically select a name.
sdv::toml::CNodeCollection tableParams = tableComponent.GetDirect("Parameters");
if (tableParams.IsValid() && tableParams.GetCount())
sComponent.ssParameterTOML = tableParams.GetTOML();
auto itComponent = std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(),
[&](const SComponent& rsComponentStored) { return rsComponentStored.ssClassName == sComponent.ssClassName &&
rsComponentStored.ssInstanceName == sComponent.ssInstanceName; });
if (itComponent == m_vecComponentList.end())
m_vecComponentList.push_back(std::move(sComponent));
}
}
catch (const sdv::toml::XTOMLParseException& rexcept)
{
SDV_LOG_ERROR("Failed to parse application configuration '", m_pathConfigFile.generic_u8string(), "': ", rexcept.what());
return false;
}
m_bLoaded = true;
return true;
}
bool CAppConfigFile::SaveConfigFile(const std::filesystem::path& rpathTarget /*= std::filesystem::path()*/,
bool bSafeParamsOnly /*= false*/) const
{
// Protect against multiple write actions at the same time.
std::string ssMutexName = "LockSdvConfigFile_" + std::to_string(GetAppSettings().GetInstanceID()) + "_" +
rpathTarget.filename().generic_u8string();
ipc::named_mutex mtx(ssMutexName);
// Warning of cppcheck for locking a local mutex, which doesn't have any effect. Since this is a named mutex between
// applciations, the warning is not correct. Suppress warning.
// cppcheck-suppress localMutex
std::unique_lock<ipc::named_mutex> lock(mtx);
std::filesystem::path pathTargetCopy = rpathTarget;
if (pathTargetCopy.empty()) pathTargetCopy = m_pathConfigFile;
if (pathTargetCopy.empty()) return false;
if (!std::filesystem::exists(pathTargetCopy.parent_path())) return false;
// If the configuration is not existing, create a default configuration file...
std::string ssConfig;
if (std::filesystem::exists(pathTargetCopy))
{
// Open the existing settings file
std::ifstream fstream(m_pathConfigFile);
if (!fstream.is_open())
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Cannot open the application configuration file." << std::endl;
return false;
}
// Read the config file
ssConfig = std::string((std::istreambuf_iterator<char>(fstream)), std::istreambuf_iterator<char>());
if (ssConfig.empty())
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Cannot read the application settings file; will use default." << std::endl;
}
}
bool bChanged = false;
if (!UpdateConfigString(ssConfig, bChanged, bSafeParamsOnly)) return false;
if (!bChanged) return true; // No change needed
std::ofstream fstream(pathTargetCopy, std::ios::trunc);
if (!fstream.is_open())
{
SDV_LOG_ERROR("Cannot write the application settings file '", m_pathConfigFile.generic_u8string(), "'.");
return false;
}
fstream << ssConfig;
return true;
}
bool CAppConfigFile::UpdateConfigString(std::string& rssContent, bool& rbChanged, bool /*bSaveParamsOnly*/ /*= false*/) const
{
// Reset change flag
rbChanged = false;
// Determine whether running in main, isolation or maintenance mode.
bool bServerApp = false;
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::maintenance:
bServerApp = true;
break;
default:
break;
}
// If the configuration is not existing, create a default configuration file...
std::string ssConfig = rssContent;
if (ssConfig.empty())
{
ssConfig = R"toml(# Configuration file
[Configuration]
Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml(
# The configuration consists of several sections:
# - Modules Modules can contain component classes, which are only made accessible, but not automatically loaded. This
# section applies to standalone applications only. Server applications use installations to provide accessible
# component classes.
# - Classes The classes that are present in a module and contain specific class information.
# - Components The components that are started when loading the configuration.
#
#
# The modules specify the location of the modules relative to the executable or with an absolute path. They are only available for
# standalone applications and are ignored by server based application, who install the modules rather than provide them in a
# configuration. A module has the following attribute:
# - Path [Compulsory] Identifying the module path of components which should be made accessible. The path can be
# relative to the application executable (or relocation path) or can be an absolute path to the module in the
# system.
#
# Example:
# [[Module]]
# Path = "my_module.sdv"
#
#
# Classes are used to specify default parameter for a component instance. These parameter can be overridden by the component instance
# when specifying the parameter with identical name in the component section. The class has the following attributes:
# - Path [Optional] Identifying the module path of the component. Only used for standalone applications. If not
# specified the component class must be known in the system (e.g. through the module section or the component
# section).
# - Class [Compulsory] Name of the class that has the default pateters.
# - Parameters [Optional] Table containing component instance specific parameters which are default parameters of all
# instances of the components with of this class.
# Example:
# [[Class]]
# Path = "my_module.sdv"
# Class = "MyComponent"
# [Class.Parameters]
# AttributeA = "default_blep"
# AttributeB = 1234
# [Component.Parameters.GroupC]
# AttributeC1 = 4567.7890
# AttributeC2 = "This is a string"
#
#
# Components are class instances that are instantiated during the loading of the configuration. They are stored as table array
# and contain as a minimum a class name. They can have instance specific parameters, which are stored as a table within the
# component. Components are instantiated in the order of appearance. A component can have the following attributes:
# - Path [Optional] Identifying the module path of the component. Only used for standalone applications. If not
# specified the component class must be known in the system (e.g. through the module section or the class
# section).
# - Class [Compulsory] Name of the class that has to be instantiated.
# - Name [Optional] Name of the component instance. If not available, the class name determines the component name.
# - Parameters [Optional] Table containing the component instance specific parameters. They override default component class
# parameters.
#
# Example:
# [[Component]]
# Path = "my_module.sdv"
# Class = "MyComponent"
# Name = "MyPersonalComponent"
# [Component.Parameters]
# AttributeA = "blep"
# AttributeB = 123
# [Component.Parameters.GroupC]
# AttributeC1 = 456.789
# AttributeC2 = "This is a text"
#
)toml";
rbChanged = true;
}
try
{
// Read the configuration
toml_parser::CParser parserConfig(ssConfig);
// Check for the version
sdv::toml::CNodeCollection nodeConfig(&parserConfig.Root());
uint32_t uiVersion = nodeConfig.GetDirect("Configuration.Version").GetValue();
if (uiVersion != SDVFrameworkInterfaceVersion)
{
SDV_LOG_ERROR("Invalid version of application settings file (expected version ", SDVFrameworkInterfaceVersion,
", but available version ", uiVersion, ")");
return false;
}
// Iterate and update through the module list (if not running as server application).
if (!bServerApp)
{
// Remove modules not in the list any more.
// Modules both in the vector and in the list are removed from the vector
// Modules leftover in the vector are added to the list
auto vecModuleListCopy = m_vecModuleList;
sdv::toml::CNodeCollection nodeModules = nodeConfig.GetDirect("Module");
for (size_t nIndex = nodeModules.GetCount() - 1; nIndex < nodeModules.GetCount(); --nIndex)
{
sdv::toml::CNodeCollection tableModule = nodeModules.Get(nIndex);
std::filesystem::path pathModule = tableModule.GetDirect("Path").GetValue().get<std::string>();
auto itModule = std::find_if(vecModuleListCopy.begin(), vecModuleListCopy.end(),
[&](const SModule& rsModule) { return pathModule == rsModule.pathModule; });
if (itModule == vecModuleListCopy.end())
{
tableModule.Delete();
rbChanged = true;
}
else
vecModuleListCopy.erase(itModule);
}
for (const SModule& rsModule : vecModuleListCopy)
{
sdv::toml::CNodeCollection tableModule = nodeConfig.AddTableArray("Module");
tableModule.AddValue("Path", rsModule.pathModule);
rbChanged = true;
}
}
// Iterate through and update the class list
// Remove classes not in the list any more.
// Classes both in the vector and in the list are removed from the vector
// Classes leftover in the vector are added to the list
auto vecClassListCopy = m_vecClassList;
sdv::toml::CNodeCollection arrayClasses = nodeConfig.GetDirect("Class");
for (size_t nIndex = arrayClasses.GetCount() -1; nIndex < arrayClasses.GetCount(); --nIndex)
{
sdv::toml::CNodeCollection tableClass = arrayClasses.Get(nIndex);
std::filesystem::path pathModule;
if (!bServerApp)
pathModule = tableClass.GetDirect("Path").GetValue().get<std::string>();
std::string ssClassName = tableClass.GetDirect("Class").GetValue().get<std::string>();
auto itClass = std::find_if(vecClassListCopy.begin(),
vecClassListCopy.end(),
[&](const sdv::SClassInfo& rsClass) { return ssClassName == rsClass.ssName; });
if (itClass == vecClassListCopy.end())
{
tableClass.Delete();
rbChanged = true;
}
else
{
// Update of path
if (!bServerApp)
{
if (pathModule.generic_u8string() != itClass->ssModulePath)
{
pathModule = std::filesystem::u8path(static_cast<std::string>(itClass->ssModulePath));
sdv::toml::CNode nodePath = tableClass.GetDirect("Path");
if (!pathModule.empty())
{
if (nodePath)
nodePath.SetValue(pathModule);
else
tableClass.AddValue("Path", pathModule);
}
else if (nodePath)
nodePath.Delete();
rbChanged = true;
}
}
// Update the parameters - class parameters are default parameters provided by the object. Overwrite existing
// default parameters.
sdv::toml::CNodeCollection tableParams = tableClass.GetDirect("Parameters");
sdv::u8string ssExistingTOML;
if (tableParams) ssExistingTOML = tableParams.GetTOML();
if (ssExistingTOML != itClass->ssDefaultConfig)
{
if (tableParams) tableParams.Delete();
if (!itClass->ssDefaultConfig.empty())
tableClass.InsertTOML(sdv::toml::npos, itClass->ssDefaultConfig);
rbChanged = true;
}
vecClassListCopy.erase(itClass);
}
}
for (const sdv::SClassInfo& rsClass : vecClassListCopy)
{
sdv::toml::CNodeCollection tableClass = nodeConfig.AddTableArray("Class");
tableClass.AddValue("Class", rsClass.ssName);
if (!bServerApp)
tableClass.AddValue("Path", std::filesystem::u8path(static_cast<std::string>(rsClass.ssModulePath)));
if (!rsClass.ssDefaultConfig.empty())
tableClass.InsertTOML(sdv::toml::npos, rsClass.ssDefaultConfig);
rbChanged = true;
}
// Iterate through and update the component list
// Remove components not in the list any more.
// Components both in the vector and in the list are removed from the vector
// Components leftover in the vector are added to the list
auto vecComponentListCopy = m_vecComponentList;
sdv::toml::CNodeCollection nodeComponents = nodeConfig.GetDirect("Component");
for (size_t nIndex = nodeComponents.GetCount() - 1; nIndex < nodeComponents.GetCount(); --nIndex)
{
sdv::toml::CNodeCollection tableComponent = nodeComponents.Get(nIndex);
std::filesystem::path pathModule;
if (!bServerApp)
pathModule = tableComponent.GetDirect("Path").GetValue().get<std::string>();
std::string ssClassName = tableComponent.GetDirect("Class").GetValue().get<std::string>();
std::string ssInstanceName = tableComponent.GetDirect("Name").GetValue().get<std::string>();
auto itComponent = std::find_if(vecComponentListCopy.begin(), vecComponentListCopy.end(),
[&](const SComponent& rsComponent)
{
return ssInstanceName.empty() ? (ssClassName == rsComponent.ssClassName && rsComponent.ssInstanceName.empty()) :
ssInstanceName == rsComponent.ssInstanceName;
});
if (itComponent == vecComponentListCopy.end())
{
// The class and the instance names must be identical
tableComponent.Delete();
rbChanged = true;
}
else
{
// Update of path
if (!bServerApp)
{
if (pathModule != itComponent->pathModule)
{
pathModule = itComponent->pathModule;
sdv::toml::CNode nodePath = tableComponent.GetDirect("Path");
if (!pathModule.empty())
{
if (nodePath)
nodePath.SetValue(pathModule);
else
tableComponent.AddValue("Path", pathModule);
}
else if (nodePath)
nodePath.Delete();
rbChanged = true;
}
}
// Update the parameters - component parameters should be updated, not simply overwritten. Use the combine function.
sdv::toml::CNodeCollection tableParams = tableComponent.GetDirect("Parameters");
sdv::u8string ssExistingTOML;
if (tableParams) ssExistingTOML = tableParams.GetTOML();
if (!itComponent->ssParameterTOML.empty())
{
// Update needed?
if (tableParams && ssExistingTOML != itComponent->ssParameterTOML)
{
// Get the underlying node collection
toml_parser::CTable* pComponent =
dynamic_cast<toml_parser::CTable*>(static_cast<sdv::IInterfaceAccess*>(tableParams.GetInterface()));
if (!pComponent)
{
SDV_LOG_ERROR("Failure to get access to an internal interface within the TOML parser.");
return false;
}
try
{
toml_parser::CParser parserParams(itComponent->ssParameterTOML);
pComponent->Combine(parserParams.Root().Cast<toml_parser::CNodeCollection>());
}
catch (const toml_parser::XTOMLParseException& /*rexcept*/)
{
SDV_LOG_WARNING("The parameter TOML cannot be interpreted; no saving possible.");
}
rbChanged = true;
}
else if (!tableParams)
{
// Simply add the parameters
tableParams = tableComponent.InsertTable(sdv::toml::npos, "Parameters");
tableParams.InsertTOML(sdv::toml::npos, itComponent->ssParameterTOML);
rbChanged = true;
}
}
vecComponentListCopy.erase(itComponent);
}
}
for (const SComponent& rsComponent : vecComponentListCopy)
{
sdv::toml::CNodeCollection tableComponent = nodeConfig.AddTableArray("Component");
if (!bServerApp && !rsComponent.pathModule.empty())
tableComponent.AddValue("Path", rsComponent.pathModule);
tableComponent.AddValue("Class", rsComponent.ssClassName);
if (!rsComponent.ssInstanceName.empty())
tableComponent.AddValue("Name", rsComponent.ssInstanceName);
if (!rsComponent.ssParameterTOML.empty())
{
sdv::toml::CNodeCollection tableParams = tableComponent.InsertTable(sdv::toml::npos, "Parameters");
tableParams.InsertTOML(sdv::toml::npos, rsComponent.ssParameterTOML);
}
rbChanged = true;
}
// Save the configuration file if needed
if (rbChanged)
{
rssContent = parserConfig.GenerateTOML();
rbChanged = true;
}
}
catch (const sdv::toml::XTOMLParseException& rexcept)
{
SDV_LOG_ERROR("Failed to parse configuration: ", rexcept.what());
return false;
}
return true;
}
const std::vector<CAppConfigFile::SComponent>& CAppConfigFile::GetComponentList() const
{
return m_vecComponentList;
}
const std::vector<sdv::SClassInfo>& CAppConfigFile::GetClassList() const
{
return m_vecClassList;
}
const std::vector<CAppConfigFile::SModule>& CAppConfigFile::GetModuleList() const
{
return m_vecModuleList;
}
bool CAppConfigFile::InsertComponent(size_t nIndex, const std::filesystem::path& rpathModule, const std::string& rssClassName,
const std::string& rssInstanceName, const TParameterVector& rvecParameters)
{
// Valid parameter?
if (rssClassName.empty()) return false;
// Check for a duplicate instance name. If there is no instance name, check for a component with the same class name without
// the instance name. This is not an error!
if (!rssInstanceName.empty())
{
if (std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(), [&](const SComponent& rsComponent)
{ return rsComponent.ssInstanceName == rssInstanceName; }) != m_vecComponentList.end())
return true;
}
else
{
if (std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(), [&](const SComponent& rsComponent)
{ return rsComponent.ssInstanceName.empty() && rsComponent.ssClassName == rssClassName; }) != m_vecComponentList.end())
return true;
}
// Check for the module to already be added to the module list. Remove it from the list if it is.
if (!rpathModule.empty())
{
auto itModule = std::find_if(m_vecModuleList.begin(), m_vecModuleList.end(), [&](const SModule& rsModule)
{ return rsModule.pathModule == rpathModule; });
if (itModule != m_vecModuleList.end()) m_vecModuleList.erase(itModule);
}
// Create a parameter TOML
std::string ssParameterTOML;
try
{
// Create a TOML string
toml_parser::CParser parser("");
sdv::toml::CNodeCollection root(&parser.Root());
// Split the key in a group and a parameter name
auto fnSplit = [](const std::string& rssKey) -> std::pair<std::string, std::string>
{
size_t nSeparator = rssKey.find_last_of('.');
if (nSeparator == std::string::npos) return std::make_pair("", rssKey);
return std::make_pair(rssKey.substr(0, nSeparator), rssKey.substr(nSeparator + 1));
};
// Iterate through the parameters
std::string ssGroup;
sdv::toml::CNodeCollection group(root);
for (const auto& prParameter : rvecParameters)
{
// Split the key in a group and parameter name.
auto prKey = fnSplit(prParameter.first);
// Need to add a group?
if (prKey.first != ssGroup)
{
group = root.InsertTable(sdv::toml::npos, prKey.first);
ssGroup = prKey.first;
}
// Add the parameter
group.InsertValue(sdv::toml::npos, prKey.second, prParameter.second);
}
ssParameterTOML = parser.GenerateTOML();
}
catch (const toml_parser::XTOMLParseException& /*rexcept*/)
{
return false;
}
// Add the instance
SComponent sComponent;
sComponent.pathModule = rpathModule;
sComponent.ssClassName = rssClassName;
sComponent.ssInstanceName = rssInstanceName;
sComponent.ssParameterTOML = std::move(ssParameterTOML);
m_vecComponentList.insert((nIndex < m_vecComponentList.size() ? m_vecComponentList.begin() + nIndex : m_vecComponentList.end()),
std::move(sComponent));
return true;
}
void CAppConfigFile::RemoveComponent(const std::string& rssInstanceName)
{
// Find and destroy
auto itComponent = std::find_if(m_vecComponentList.begin(),
m_vecComponentList.end(),
[&](const SComponent& rsComponent) { return rsComponent.ssInstanceName == rssInstanceName; });
if (itComponent != m_vecComponentList.end())
m_vecComponentList.erase(itComponent);
}
bool CAppConfigFile::InsertModule(size_t nIndex, const std::filesystem::path& rpathModule)
{
// Check for the proper context; only allowed when running as standalone (not main, isolated or maintenace).
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::maintenance:
return true;
default:
break;
}
if (rpathModule.empty())
return false;
// Check for the module to exist already in either the component or in the module list
if (std::find_if(m_vecComponentList.begin(),
m_vecComponentList.end(),
[&](const SComponent& rsComponent) { return rsComponent.pathModule == rpathModule; })
!= m_vecComponentList.end())
return true;
if (std::find_if(m_vecModuleList.begin(),
m_vecModuleList.end(),
[&](const SModule& rsModule) { return rsModule.pathModule == rpathModule; })
!= m_vecModuleList.end())
return true;
// Add the module to the module list
SModule sModule;
sModule.pathModule = rpathModule;
m_vecModuleList.insert(
(nIndex < m_vecModuleList.size() ? m_vecModuleList.begin() + nIndex : m_vecModuleList.end()), std::move(sModule));
return true;
}
void CAppConfigFile::RemoveModule(const std::filesystem::path& rpathModule)
{
// Check for the proper context; only allowed when running as standalone (not main, isolated or maintenace).
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::maintenance:
return;
default:
break;
}
// Find and destroy (any module and component with the provided path).
auto itModule = std::find_if(m_vecModuleList.begin(),
m_vecModuleList.end(),
[&](const SModule& rsModule) { return rsModule.pathModule == rpathModule; });
if (itModule != m_vecModuleList.end())
m_vecModuleList.erase(itModule);
while (true)
{
auto itComponent = std::find_if(m_vecComponentList.begin(),
m_vecComponentList.end(),
[&](const SComponent& rsComponent) { return rsComponent.pathModule == rpathModule; });
if (itComponent == m_vecComponentList.end())
break;
m_vecComponentList.erase(itComponent);
}
}
CAppConfigFile::EMergeResult CAppConfigFile::MergeConfigFile(const std::filesystem::path& rpathConfigFile)
{
CAppConfigFile configNew(rpathConfigFile);
if (!configNew.LoadConfigFile()) return EMergeResult::not_successful;
size_t nSucceeded = 0;
size_t nFailed = 0;
// Add modules
auto vecNewModules = configNew.GetModuleList();
for (const auto& sNewModule : vecNewModules)
{
if (std::find_if(m_vecModuleList.begin(), m_vecModuleList.end(), [&sNewModule](const SModule& rsExistingModule)
{ return sNewModule.pathModule == rsExistingModule.pathModule; }) == m_vecModuleList.end())
m_vecModuleList.push_back(sNewModule);
++nSucceeded;
}
// Add/update classes
auto vecNewClasses = configNew.GetClassList();
for (const auto& sNewClass : vecNewClasses)
{
auto itClass = std::find_if(m_vecClassList.begin(), m_vecClassList.end(),
[&sNewClass](const sdv::SClassInfo& rsExistingClass)
{ return sNewClass.ssName == rsExistingClass.ssName; });
if (itClass == m_vecClassList.end())
m_vecClassList.push_back(sNewClass);
else
*itClass = sNewClass;
++nSucceeded;
}
// Merge components
auto vecNewComponents = configNew.GetComponentList();
for (const auto& sNewComponent : vecNewComponents)
{
auto itComponent = std::find_if(m_vecComponentList.begin(), m_vecComponentList.end(),
[&sNewComponent](const SComponent& rsExistingComponent)
{
if (sNewComponent.ssInstanceName.empty())
{
if (!rsExistingComponent.ssInstanceName.empty()) return false;
return sNewComponent.ssClassName == rsExistingComponent.ssClassName;
}
else
return sNewComponent.ssInstanceName == rsExistingComponent.ssInstanceName;
});
if (itComponent == m_vecComponentList.end())
m_vecComponentList.push_back(sNewComponent);
else
{
// Only update component information if the components are of identical type
if (itComponent->ssClassName != sNewComponent.ssClassName)
{
++nFailed;
continue;
}
// TODO: Add additional possibilty to replace the parameters
// Merge the parameters of the existing component.
if (!sNewComponent.ssParameterTOML.empty())
{
if (itComponent->ssParameterTOML.empty())
itComponent->ssParameterTOML = sNewComponent.ssParameterTOML;
else
try
{
toml_parser::CParser parserNew(itComponent->ssParameterTOML), parserExisting(sNewComponent.ssParameterTOML);
parserExisting.Root().Combine(parserNew.Root().Cast<toml_parser::CNodeCollection>());
itComponent->ssParameterTOML = parserExisting.GenerateTOML();
}
catch (const toml_parser::XTOMLParseException&)
{
++nFailed;
continue;
}
}
++nSucceeded;
}
}
return nFailed ? (nSucceeded ? EMergeResult::partly_successfull : EMergeResult::not_successful) : EMergeResult::successful;
}

View File

@@ -0,0 +1,297 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef APP_CONFIG_FILE_H
#define APP_CONFIG_FILE_H
#include <filesystem>
#include <string>
#include <vector>
#include <interfaces/config.h>
/**
* @brief Parameter vector type holding parameter name and parameter value paires.
*/
using TParameterVector = std::vector<std::pair<std::string, sdv::any_t>>;
/**
* @brief Application configuration file loading and saving the configuration in a TOML file.
* @details The configuration file is a TOML and follows the specification of TOML 1.0 (https://toml.io/en/v1.0.0). The
* configuration file starts with the "Configuration" table, which identifies that this a configuration file. The table needs to
* contain the version of the configuration. For example:
* @code
* [Configuration]
* Version = 100
* @endcode
*
* The configuration consists of several sections:
* - Modules Modules can contain component classes, which are only made accessible, but not automatically loaded. This
* section applies to standalone applications only. Server applications use installations to provide accessible
* component classes.
* - Classes The classes that are present in a module and contain specific class information.
* - Components The components that are started when loading the configuration.
*
* The modules specify the location of the modules relative to the executable or with an absolute path. They are only available for
* standalone applications and are ignored by server based application, who install the modules rather than provide them in a
* configuration. A module has the following attribute:
* - Path [Compulsory] Identifying the module path of components which should be made accessible. The path can be
* relative to the application executable (or relocation path) or can be an absolute path to the module in the
* system.
* Example:
* @code
* [[Module]]
* Path = "my_module.sdv"
* @endcode
*
* Classes are used to specify default parameter for a component instance. These parameter can be overridden by the component instance
* when specifying the parameter with identical name in the component section. The class has the following attributes:
* - Path [Optional] Identifying the module path of the component. Only used for standalone applications. If not
* specified the component class must be known in the system (e.g. through the module section or the component
* section).
* - Name [Compulsory] Name of the class.
* - Aliases [Optional] Array of strings with alternative names for this class.
* - DefaultName [Optional] Name to use when instantiating the component. If not specified, the class name will be used.
* - Type [Compulsory] Object type this class represents.
* - Singleton [Optional] Boolean specifying whether the component allows more than one instantiation. Default is false.
* - Dependencies [Optional] Array of strings with component instances that this class depends on.
* - Parameters [Optional] Table containing component instance specific parameters which are default parameters of all
* instances of the components with of this class.
* Example:
* @code
* [[Class]]
* Path = "my_module.sdv"
* Name = "MyComponent"
* Aliases = ["Component1", "ComponentA"]
* DefaultName = "MyComp"
* Type = "Device"
* Singleton = false
* Dependencies = ["Component15", "Component16"]
* [Class.Parameters]
* AttributeA = "default_blep"
* AttributeB = 1234
* AttributeC = 4567.7890
*@endcode
*
* Components are class instances that are instantiated during the loading of the configuration. They are stored as table array
* and contain as a minimum a class name. They can have instance specific parameters, which are stored as a table within the
* component. Components are instantiated in the order of appearance. A component can have the following attributes:
* - Path [Optional] Identifying the module path of the component. Only used for standalone applications. If not
* specified the component class must be known in the system (e.g. through the module section or the class
* section).
* - Class [Compulsory] Name of the class that has to be instantiated.
* - Name [Optional] Name of the component instance. If not available, the class name determines the component name.
* - Parameters [Optional] Table containing the component instance specific parameters. They override default component class
* parameters.
* Example:
* @code
* [[Component]]
* Path = "my_module.sdv"
* Class = "MyComponent"
* Name = "MyPersonalComponent"
* [Component.Parameters]
* AttributeA = "blep"
* AttributeB = 123
* AttributeC = 456.789
* AttributeD = [1, 2, 3, 4]
* @endcode
*/
class CAppConfigFile
{
public:
/**
* @brief default constructor.
*/
CAppConfigFile() = default;
/**
* @brief Constructor with the config path. The config file is automatically loaded.
* @param[in] rpathConfigFile Path to the configuration. The path must be a file name when running in main, isolation or
* maintenance mode. Otherwise the path should contain the location of the config file.
* The file path will be located in the root directory of the instance.
*/
CAppConfigFile(const std::filesystem::path& rpathConfigFile);
/**
* @brief Clear the configuration.
*/
void Clear();
/**
* @brief Is the configuration loaded or created?
* @return Returns whether the file is loaded.
*/
bool IsLoaded() const;
/**
* @brief Get the configuration file path.
* @return The config file path.
*/
const std::filesystem::path& ConfigPath() const;
/**
* @brief Load the application config file.
* @remarks When there is no config file, this is not an error.
* @return Returns whether the loading was successful.
*/
bool LoadConfigFile();
/**
* @brief Load the application config from string.
* @param[in] rssConfig Reference to the configuration string.
* @return Returns whether the loading was successful.
*/
bool LoadConfigFromString(const std::string& rssConfig);
/**
* @brief Save the application config file (or create when not existing yet).
* @param[in] rpathTarget Reference to a target path. When set, the file is saved to the target file. If not set, the original
* file is used.
* @param[in] bSafeParamsOnly When set, only the parameters are stored in the configuration.
* @return Returns whether the saving was successful (returns 'true' when there is no change is needed).
*/
bool SaveConfigFile(const std::filesystem::path& rpathTarget = std::filesystem::path(), bool bSafeParamsOnly = false) const;
/**
* @brief Update the configuration string with the stored configuration.
* @param[in, out] rssContent Reference to the string providing the current configuration being updated by this function.
* @param[in, out] rbChanged Reference to the boolean being set when the string was changed.
* @param[in] bSaveParamsOnly When set, only the parameters are stored in the configuration.
* @return Returns whether the update was successful (even when no change occurred).
*/
bool UpdateConfigString(std::string& rssContent, bool& rbChanged, bool bSaveParamsOnly = false) const;
/**
* @brief Module structure (only used in standalone applications).
*/
struct SModule
{
std::filesystem::path pathModule; ///< Component module path.
};
///**
// * @brief Component class structure (containing default parameters).
// */
//struct SClass
//{
// std::filesystem::path pathModule; ///< Optional class module path. Only used in standalone applications.
// std::string ssClassName; ///< Class name of the component
// std::string ssParameterTOML; ///< Parameter configuration (excluding [Parameters]-group).
//};
/**
* @brief Component structure
*/
struct SComponent
{
std::filesystem::path pathModule; ///< Optional component module path. Only used in standalone applications.
std::string ssClassName; ///< Class name of the component.
std::string ssInstanceName; ///< Optional instance name. If not provided, will be identical to the class
///< name.
std::string ssParameterTOML; ///< Parameter configuration (excluding [Parameters]-group).
};
/**
* @brief Get the component list.
* @return Reference to the vector containing the components in order of the configuration.
*/
const std::vector<SComponent>& GetComponentList() const;
/**
* @brief Get the class list.
* @remarks Only available when running as standalone application.
* @return Reference to the vector containing the classes in order of the configuration.
*/
const std::vector<sdv::SClassInfo>& GetClassList() const;
/**
* @brief Get the module list.
* @remarks Only available when running as standalone application.
* @return Reference to the vector containing the modules in order of the configuration.
*/
const std::vector<SModule>& GetModuleList() const;
/**
* @brief Insert a component at the provided location.
* @param[in] nIndex The index location to insert the component before or when larger than the component list, at the end of
* the component list.
* @param[in] rpathModule Reference to the path of the module. Only used when running as standalone application.
* @param[in] rssClassName Reference to the string containing the module class name of the class to be instantiated when
* loading the configuration.
* @param[in] rssInstanceName Reference to the string containing an optional instantiating name of the component instance. Using
* additional names allows multiple instantiation of the same component.
* @param[in] rvecParameters Reference to the optional parameter vector belonging to the object instance.
* @return Returns whether inserting the component was successful. If a component with the same class and instance names and
* the same path is installed, the function succeeds. In all other cases, having a duplicate class or instance name will result
* in a function failure.
*/
bool InsertComponent(size_t nIndex, const std::filesystem::path& rpathModule, const std::string& rssClassName,
const std::string& rssInstanceName, const TParameterVector& rvecParameters);
/**
* @brief Remove the component with the provided instance name.
* @param[in] rssInstanceName Reference to the instance name of the component. If the component uses the class name for its
* instance, the class name will be used for the search.
*/
void RemoveComponent(const std::string& rssInstanceName);
/**
* @brief Insert a module into the configuration. Modules listed in the configuration are loaded, but components are not
* started automatically. This might be useful if the module contains utilities that might be loaded on demand.
* @remarks Modules can only be added when running as standalone application.
* @remarks The modules are only added once. If a module is present in either the component or the module list, it is not added
* again, and the function returns 'true'.
* @param[in] nIndex The index location to insert the module before or when larger than the module list, at the end of the
* module list.
* @param[in] rpathModule Reference to the path of the module.
* @return Returns whether inserting the module was successful. If a module with the same path is installed, the function
* succeeds.
*/
bool InsertModule(size_t nIndex, const std::filesystem::path& rpathModule);
/**
* @brief Remove the module with the provided module name.
* @remarks Removing the module will also remove any components that are registered for the module.
* @remarks Modules can only be removed when running as standalone application.
* @param[in] rpathModule Reference to the path of the module.
*/
void RemoveModule(const std::filesystem::path& rpathModule);
enum class EMergeResult
{
successful,
partly_successfull,
not_successful
};
/**
* @brief Merge a configuration into this configuration.
* @details All modules, classes and components will be added or updated. If a classes already exist, the class parameters will
* be overwritten (or removed if present before). If a component already exist, the component parameters will be updated (new
* parameters added, parameters existing in both configurations updated and other parameters left unchanged).
* @param[in] rpathConfigFile Reference to the config file to merge.
* @return Returns whether the configuration could be merged.
*/
EMergeResult MergeConfigFile(const std::filesystem::path& rpathConfigFile);
private:
std::filesystem::path m_pathConfigFile; ///< Path to the configuration file. If running as main, isolated or
///< maintenance application, the config file must be located at the
///< installation directory.
bool m_bLoaded = false; ///< Set when the configuration has loaded.
std::vector<SComponent> m_vecComponentList; ///< Component list containing instantiation info for components.
std::vector<sdv::SClassInfo> m_vecClassList; ///< Class list containing default parameters for component classes.
std::vector<SModule> m_vecModuleList; ///< Module list containing the module paths.
};
#endif // !defined APP_CONFIG_FILE_H

View File

@@ -1,40 +1,26 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "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";
#include "app_settings.h"
#include "logger_control.h"
#include "app_config.h"
/**
* @brief Specific exit handler helping to shut down after startup. In case the shutdown wasn't explicitly executed.
@@ -53,55 +39,10 @@ void ExitHandler()
}
}
CAppControl::CAppControl()
{}
CAppControl::~CAppControl()
{}
bool CAppControl::IsMainApplication() const
CAppControl& GetAppControl()
{
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;
static CAppControl app_control;
return app_control;
}
bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfaceAccess* pEventHandler)
@@ -122,7 +63,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
BroadcastOperationState(sdv::app::EAppOperationState::initializing);
// Process the application config.
bool bRet = ProcessAppConfig(ssConfig);
bool bRet = GetAppSettings().ProcessAppStartupConfig(ssConfig);
// Undo logging interception
sstreamCOUT.rdbuf()->pubsync();
@@ -138,16 +79,16 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
std::cout << sstreamCOUT.str();
std::clog << sstreamCLOG.str();
std::cerr << sstreamCERR.str();
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
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)
if (GetAppSettings().IsMainApplication())
{
m_fifoTraceStreamBuffer.SetInstanceID(m_uiInstanceID);
m_fifoTraceStreamBuffer.SetInstanceID(GetAppSettings().GetInstanceID());
m_fifoTraceStreamBuffer.Open(1000);
std::cout << "**********************************************" << std::endl;
}
@@ -158,18 +99,18 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
std::cerr << sstreamCERR.str();
// Check for a correctly opened stream buffer
if (m_eContextMode == sdv::app::EAppContext::main && !m_fifoTraceStreamBuffer.IsOpened())
if (GetAppSettings().IsMainApplication() && !m_fifoTraceStreamBuffer.IsOpened())
{
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
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)
if (GetAppSettings().IsMainApplication())
{
m_pathLockFile = GetExecDirectory() / ("sdv_core_" + std::to_string(m_uiInstanceID) + ".lock");
m_pathLockFile = GetExecDirectory() / ("sdv_core_" + std::to_string(GetAppSettings().GetInstanceID()) + ".lock");
#ifdef _WIN32
// Checkf or the existence of a lock file. If existing, do not continue.
if (std::filesystem::exists(m_pathLockFile)) return false;
@@ -214,19 +155,19 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
// Load the core services
if (!fnLoadModule("core_services.sdv"))
{
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
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())
if (!GetAppSettings().GetLoggerModulePath().empty())
{
m_tLoggerModuleID = fnLoadModule(m_pathLoggerModule.u8string());
m_tLoggerModuleID = fnLoadModule(GetAppSettings().GetLoggerModulePath().u8string());
if (!m_tLoggerModuleID)
{
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load the custom logger. Cannot continue!" << std::endl;
Shutdown(true);
return false;
@@ -234,12 +175,12 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
}
// Start the logger and assign it to the logger control.
fnCreateObject(m_ssLoggerClass, m_ssLoggerClass, "");
sdv::IInterfaceAccess* pLoggerObj = GetRepository().GetObject(m_ssLoggerClass);
fnCreateObject(GetAppSettings().GetLoggerClass(), GetAppSettings().GetLoggerClass(), "");
sdv::IInterfaceAccess* pLoggerObj = GetRepository().GetObject(GetAppSettings().GetLoggerClass());
if (!pLoggerObj)
{
GetRepository().DestroyObject2(m_ssLoggerClass);
if (!m_bSilent)
GetRepository().DestroyObject2(GetAppSettings().GetLoggerClass());
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl;
Shutdown(true);
return false;
@@ -249,14 +190,14 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
sdv::core::ILogger* pLogger = pLoggerObj->GetInterface<sdv::core::ILogger>();
if (!pLoggerConfig || !pLogger)
{
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
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);
if (!GetAppSettings().GetLoggerProgramTag().empty())
pLoggerConfig->SetProgramTag(GetAppSettings().GetLoggerProgramTag());
pLoggerConfig->SetLogFilter(GetAppSettings().GetLoggerSeverityFilter(), GetAppSettings().GetConsoleSeverityFilter());
GetLoggerControl().SetLogger(pLogger);
// Create the core service objects
@@ -266,7 +207,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
bRet = bRet && fnCreateObject("ConfigService", "ConfigService", "");
if (!bRet)
{
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
{
std::cerr << "ERROR: Failed to start the Core Services. Cannot continue!" << std::endl;
if (!ssErrorString.empty())
@@ -278,8 +219,7 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
// Load specific services
bool bLoadRPCClient = false, bLoadRPCServer = false;
bool bLoadInstallationManifests = false;
switch (m_eContextMode)
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::standalone:
break;
@@ -287,11 +227,9 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
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;
@@ -302,22 +240,28 @@ bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfac
bLoadRPCClient = true;
break;
default:
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
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)
// Load the application settings.
if (!GetAppSettings().LoadSettingsFile())
{
if (!GetAppConfig().LoadInstallationManifests())
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to load installation manifests. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load application settings file. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Load installation manifests. For standalone applications, only the core manifest is loaded.
if (!GetAppConfig().LoadInstallationManifests())
{
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load installation manifests. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Load process control
@@ -360,7 +304,7 @@ Type = "Local"
if (!bRet)
{
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load IPC server components. Cannot continue!" << std::endl;
if (!ssErrorString.empty())
std::cerr << "REASON: " << ssErrorString << std::endl;
@@ -394,7 +338,7 @@ Type = "Local"
if (!bRet)
{
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to load IPC client components. Cannot continue!" << std::endl;
Shutdown(true);
return false;
@@ -403,7 +347,7 @@ Type = "Local"
if (!bRet)
{
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Failed to start core components. Cannot continue!" << std::endl;
if (!ssErrorString.empty())
std::cerr << "REASON: " << ssErrorString << std::endl;
@@ -418,15 +362,15 @@ Type = "Local"
SetConfigMode();
if (IsMainApplication())
if (GetAppSettings().IsMainApplication())
{
// Load system configs - they need to be present completely
for (const std::filesystem::path& rpathConfig : m_vecSysConfigs)
for (const std::filesystem::path& rpathConfig : GetAppSettings().GetSystemConfigPaths())
{
sdv::core::EConfigProcessResult eResult = GetAppConfig().LoadConfig(rpathConfig.generic_u8string());
if (eResult != sdv::core::EConfigProcessResult::successful)
{
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Cannot load or partly load the system configuration: " <<
rpathConfig.generic_u8string() << std::endl;
Shutdown(true);
@@ -438,22 +382,25 @@ Type = "Local"
GetAppConfig().ResetConfigBaseline();
// Load the application config - they can also be partly there
if (!m_pathAppConfig.empty())
if (!GetAppSettings().GetUserConfigPath().empty())
{
sdv::core::EConfigProcessResult eResult = GetAppConfig().LoadConfig(m_pathAppConfig.generic_u8string());
sdv::core::EConfigProcessResult eResult =
GetAppConfig().LoadConfig(GetAppSettings().GetUserConfigPath().generic_u8string());
if (eResult == sdv::core::EConfigProcessResult::failed)
{
if (!m_bSilent)
std::cerr << "ERROR: Cannot load application configuration: " << m_pathAppConfig.generic_u8string() << std::endl;
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "ERROR: Cannot load application configuration: " <<
GetAppSettings().GetUserConfigPath().generic_u8string() << std::endl;
Shutdown(true);
return false;
}
else if (eResult != sdv::core::EConfigProcessResult::partially_successful)
{
if (!m_bSilent)
if (!GetAppSettings().IsConsoleSilent())
std::cerr << "WARNING: Partially could not load application configuration: " <<
m_pathAppConfig.generic_u8string() << std::endl;
GetAppSettings().GetUserConfigPath().generic_u8string() << std::endl;
}
m_bAutoSaveConfig = true;
}
}
@@ -477,7 +424,7 @@ void CAppControl::RunLoop()
// Treat local running differently from main and isolated app running.
// Note... Maintenance apps should not use the loop
bool bLocal = true;
switch (m_eContextMode)
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
@@ -494,12 +441,12 @@ void CAppControl::RunLoop()
CShutdownRequestListener listener;
if (bLocal)
{
listener = std::move(CShutdownRequestListener(m_uiInstanceID));
listener = std::move(CShutdownRequestListener(GetAppSettings().GetInstanceID()));
if (!listener.IsValid())
throw sdv::XAccessDenied(); // Another instance is already running.
}
if (m_bVerbose)
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Entering loop" << std::endl;
m_bRunLoop = true;
@@ -520,7 +467,7 @@ void CAppControl::RunLoop()
m_pEvent->ProcessEvent(sEvent);
}
}
if (m_bVerbose)
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Leaving loop" << std::endl;
}
@@ -543,8 +490,11 @@ void CAppControl::Shutdown(/*in*/ bool bForce)
// Disable automatic configuration saving.
m_bAutoSaveConfig = false;
// Update the application settings file
GetAppSettings().SaveSettingsFile();
// Destroy all objects... this should also remove any registered services, except the custom logger.
GetRepository().DestroyAllObjects(std::vector<std::string>({ m_ssLoggerClass }), bForce);
GetRepository().DestroyAllObjects(std::vector<std::string>({GetAppSettings().GetLoggerClass()}), bForce);
// Unload all modules... this should destroy all running services, except the custom logger
GetModuleControl().UnloadAll(std::vector<sdv::core::TModuleID>({ m_tLoggerModuleID }));
@@ -587,7 +537,7 @@ void CAppControl::Shutdown(/*in*/ bool bForce)
}
// End trace streaming
if (m_eContextMode == sdv::app::EAppContext::main)
if (GetAppSettings().IsMainApplication())
{
std::cout << "**********************************************" << std::endl;
@@ -596,22 +546,11 @@ void CAppControl::Shutdown(/*in*/ bool bForce)
}
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;
GetAppSettings().Reset();
}
void CAppControl::RequestShutdown()
@@ -625,11 +564,6 @@ sdv::app::EAppOperationState CAppControl::GetOperationState() const
return m_eState;
}
uint32_t CAppControl::GetInstance() const
{
return m_uiInstanceID;
}
void CAppControl::SetConfigMode()
{
GetRepository().SetConfigMode();
@@ -644,42 +578,6 @@ void CAppControl::SetRunningMode()
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;
@@ -693,20 +591,11 @@ void CAppControl::EnableAutoConfigUpdate()
void CAppControl::TriggerConfigUpdate()
{
if (!m_bAutoSaveConfig || !m_bEnableAutoSave) return;
if (m_pathAppConfig.empty()) return;
if (GetAppSettings().GetUserConfigPath().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;
if (!GetAppConfig().SaveConfig(GetAppSettings().GetUserConfigPath().generic_u8string()))
SDV_LOG_ERROR("Failed to automatically save the configuration ", GetAppSettings().GetUserConfigPath().generic_u8string());
}
void CAppControl::BroadcastOperationState(sdv::app::EAppOperationState eState)
@@ -721,255 +610,7 @@ void CAppControl::BroadcastOperationState(sdv::app::EAppOperationState eState)
}
}
bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig)
{
toml_parser::CParser 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.Root().Direct("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<toml_parser::CNode> ptrLogHandlerPath = parserConfig.Root().Direct("LogHandler.Path");
std::shared_ptr<toml_parser::CNode> ptrLogHandlerClass = parserConfig.Root().Direct("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<toml_parser::CNode> ptrLogPogramTag = parserConfig.Root().Direct("LogHandler.Tag");
if (ptrLogPogramTag) m_ssProgramTag = static_cast<std::string>(ptrLogPogramTag->GetValue());
// Get the application-mode
std::string ssApplication = "Standalone";
std::shared_ptr<toml_parser::CNode> ptrApplication = parserConfig.Root().Direct("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<toml_parser::CNode> ptrLogSeverityFilter = parserConfig.Root().Direct("LogHandler.Filter");
m_eSeverityFilter = fnTranslateSevFilter(ptrLogSeverityFilter ? ptrLogSeverityFilter->GetValue() : "",
sdv::core::ELogSeverity::info);
ptrLogSeverityFilter = parserConfig.Root().Direct("LogHandler.ViewFilter");
m_eSeverityViewFilter = fnTranslateSevFilter(ptrLogSeverityFilter ? ptrLogSeverityFilter->GetValue() : "",
eLogDefaultViewSeverityFilter);
// Get the optional instance ID.
std::shared_ptr<toml_parser::CNode> ptrInstance = parserConfig.Root().Direct("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<toml_parser::CNode> ptrRetries = parserConfig.Root().Direct("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<toml_parser::CNode> ptrInstallDir = parserConfig.Root().Direct("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.Root().Direct("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
toml_parser::CParser parserSettings(ssContent);
// Check for the version
auto ptrVersion = parserSettings.Root().Direct("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.Root().Direct("Settings.SystemConfig");
if (ptrSystemConfigs && ptrSystemConfigs->Cast<toml_parser::CArray>())
{
for (uint32_t uiIndex = 0; uiIndex < ptrSystemConfigs->Cast<toml_parser::CArray>()->GetCount(); uiIndex++)
{
auto ptrSystemConfig = ptrSystemConfigs->Cast<toml_parser::CArray>()->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.Root().Direct("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();
return ::GetAppSettings().IsMainApplication() || ::GetAppSettings().IsIsolatedApplication();
}
#endif

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef APP_CONTROL_H
#define APP_CONTROL_H
@@ -6,136 +19,53 @@
#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
/**
* @brief Application control class.
* @details The application control class is responsible for the startup and shutdown of the system. The startup behavior can be
* influenced through the provided startup configuration. Dependable on the configuration, the core context is either server
* operated (for main, isolated or maintenance applications) or locally operated (for standalone, external or essential
* application).
*
* In case of a server operated startup sequence, the following startup procedure takes place:
* - Core services are started
* - Installation manifests are loaded (not for maintenance applications)
* - This registers the available component classes and corresponding modules
* - IPC and RPC services are started
* - Switch system to configuration mode
* - Installed system configurations are loaded and components are started
* - This starts all system, device and interface abstraction level components with corresponding configuration.
* - Installed user configuration will be loaded and components are started
* - This will start the vehicle functions (in isolated processed where applicable).
* - Switch system to running mode
*
* In case of a locally operated startup sequence, the following startup procedure takes place:
* - Core services are started
* - Core installation manifest is loaded
* - This registers the available component classes and corresponding modules for core components
* - For external application: RPC client is started
* - Switch system to configuration mode
* - Modules from provided configuration are loaded
* - This registers the available component classes and corresponding modules
* - Installed user configuration will be loaded and components are started
* - This will start the vehicle functions.
* - Switch system to running mode
*/
class CAppControl : public sdv::IInterfaceAccess, public sdv::app::IAppControl, public sdv::app::IAppOperation,
public sdv::app::IAppShutdownRequest
{
public:
/**
* @brief Constructor
*/
CAppControl();
/**
* @brief Destructor
*/
~CAppControl();
CAppControl() = default;
// 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
@@ -178,15 +108,6 @@ public:
*/
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.
*/
@@ -197,41 +118,6 @@ public:
*/
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.
*/
@@ -247,18 +133,6 @@ public:
*/
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.
@@ -266,41 +140,24 @@ private:
*/
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.
bool m_bAutoSaveConfig = false; ///< System setting for automatic saving of the user configuration.
};
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
/**
* @brief Return the application control.
* @return Reference to the application control.
*/
CAppControl& GetAppControl();
/**
* @brief App config service class.
@@ -313,7 +170,6 @@ public:
// 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())
@@ -321,24 +177,16 @@ public:
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
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
DEFINE_SDV_OBJECT(CAppControlService)
#endif // !defined APP_CONTROL_H

View File

@@ -0,0 +1,714 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "app_settings.h"
#include "toml_parser/parser_toml.h"
#include "../../global/flags.h"
#include "../../global/exec_dir_helper.h"
#include "../../global/ipc_named_mutex.h"
#include <support/toml.h>
CAppSettings& GetAppSettings()
{
static CAppSettings app_settings;
return app_settings;
}
CAppSettings::CAppSettings()
{}
CAppSettings::~CAppSettings()
{}
bool CAppSettings::ProcessAppStartupConfig(const sdv::u8string& rssConfig)
{
toml_parser::CParser parserStartupConfig;
std::string ssError;
try
{
// Read the configuration
if (!parserStartupConfig.Process(rssConfig))
return false;
}
catch (const sdv::toml::XTOMLParseException& rexcept)
{
ssError = std::string("ERROR: Failed to parse application configuration: ") + rexcept.what();
}
sdv::toml::CNodeCollection tableStartupConfig(&parserStartupConfig.Root());
// Get the reporting settings (if this succeeded at all...)
m_bSilent = tableStartupConfig.GetDirect("Console.Report").GetValue().get<std::string>() == "Silent";
m_bVerbose = tableStartupConfig.GetDirect("Console.Report").GetValue().get<std::string>() == "Verbose";
// 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
m_pathLoggerModule = tableStartupConfig.GetDirect("LogHandler.Path").GetValue().get<std::string>();
m_ssLoggerClass = tableStartupConfig.GetDirect("LogHandler.Class").GetValue().get<std::string>();
if (m_ssLoggerClass.empty())
m_ssLoggerClass = "DefaultLoggerService";
// Get an optional program tag for the logger
m_ssProgramTag = tableStartupConfig.GetDirect("LogHandler.Tag").GetValue().get<std::string>();
// Get the application-mode
std::string ssApplication = tableStartupConfig.GetDirect("Application.Mode").GetValue();
if (ssApplication.empty()) ssApplication = "Standalone";
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;
m_eSeverityFilter = fnTranslateSevFilter(tableStartupConfig.GetDirect("LogHandler.Filter").GetValue(),
sdv::core::ELogSeverity::info);
m_eSeverityViewFilter = fnTranslateSevFilter(
tableStartupConfig.GetDirect("LogHandler.ViewFilter").GetValue(), eLogDefaultViewSeverityFilter);
// Get the optional instance ID.
sdv::any_t anyInstanceID = tableStartupConfig.GetDirect("Application.Instance").GetValue();
if (anyInstanceID) m_uiInstanceID = anyInstanceID;
else
m_uiInstanceID = 1000u;
// Number of attempts to establish a connection to a running instance.
m_uiRetries = tableStartupConfig.GetDirect("Application.Retries").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() || IsMaintenanceApplication())
{
// Get the optional installation directory.
m_pathRootDir = tableStartupConfig.GetDirect("Application.InstallDir").GetValueAsPath();
if (!m_pathRootDir.empty())
{
if (m_pathRootDir.is_relative())
m_pathRootDir = GetExecDirectory() / m_pathRootDir;
}
else
m_pathRootDir = GetExecDirectory();
m_pathInstallDir = m_pathRootDir / std::to_string(m_uiInstanceID);
try
{
std::filesystem::create_directories(m_pathInstallDir);
}
catch (const std::filesystem::filesystem_error& rexcept)
{
if (!m_bSilent)
{
std::cerr << "Cannot create installation directory: " << m_pathInstallDir << std::endl;
std::cerr << " Reason: " << rexcept.what() << std::endl;
}
return false;
}
}
// Maintenance, main and isolated applications cannot load specific configs. The others can specify a configuration file, but
// not auto-updateable.
if (!IsMainApplication() && !IsMaintenanceApplication() && !IsIsolatedApplication())
m_pathUserConfig = tableStartupConfig.GetDirect("Application.Config").GetValueAsPath();
// Read the settings... if existing. And only for the main application
if (IsMainApplication() && !LoadSettingsFile())
return false;
return true;
}
bool CAppSettings::LoadSettingsFile()
{
// Check for the proper context
switch (m_eContextMode)
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::maintenance:
break;
default:
return true; // Not an error...
}
// If the template is not existing, this is not an error...
if (!std::filesystem::exists(m_pathInstallDir / "settings.toml"))
return true;
std::ifstream fstream(m_pathInstallDir / "settings.toml");
std::string ssSettings((std::istreambuf_iterator<char>(fstream)), std::istreambuf_iterator<char>());
fstream.close();
try
{
// Read the configuration
toml_parser::CParser parserSettings(ssSettings);
// If there is no "Settings" table, this is not an error...
sdv::toml::CNodeCollection tableSettings(parserSettings.Root().GetNodeDirect("Settings"));
if (!tableSettings) return true;
// Check for the version
uint32_t uiVersion = tableSettings.GetDirect("Version").GetValue();
if (uiVersion != SDVFrameworkInterfaceVersion)
{
if (!m_bSilent)
std::cerr << "ERROR: Invalid version of application settings file (expected version "
<< SDVFrameworkInterfaceVersion << ", but available version " << uiVersion << ")" << std::endl;
return false;
}
// Get the platform config - but only when not specified over the app-control-config.
if (m_pathPlatformConfig.empty())
m_pathPlatformConfig = tableSettings.GetDirect("PlatformConfig").GetValueAsPath();
// Get the vehicle interface config - but only when not specified over the app-control-config.
if (m_pathVehIfcConfig.empty())
m_pathVehIfcConfig = tableSettings.GetDirect("VehIfcConfig").GetValueAsPath();
// Get the vehicle abstraction config - but only when not specified over the app-control-config.
if (m_pathVehAbstrConfig.empty())
m_pathVehAbstrConfig = tableSettings.GetDirect("VehAbstrConfig").GetValueAsPath();
// Get the application config - but only when not specified over the app-control-config.
if (m_pathUserConfig.empty())
m_pathUserConfig = tableSettings.GetDirect("AppConfig").GetValueAsPath();
}
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;
}
bool CAppSettings::SaveSettingsFile()
{
// Check for the proper context
switch (m_eContextMode)
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::maintenance:
break;
default:
return true; // Not an error...
}
// Protect against multiple write actions at the same time.
ipc::named_mutex mtx("LockSdvAppSettings_" + std::to_string(m_uiInstanceID));
// Warning of cppcheck for locking a local mutex, which doesn't have any effect. Since this is a named mutex between
// applciations, the warning is not correct. Suppress warning.
// cppcheck-suppress localMutex
std::unique_lock<ipc::named_mutex> lock(mtx);
const std::string ssSettingsTemplate = R"toml(# Settings file
[Settings]
Version = )toml" + std::to_string(SDVFrameworkInterfaceVersion) + R"toml(
# The system configuration is divided into:
# 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 vehicle abstraction services
# The configuration files are loaded exactly in that order, allowing the vehicle interface to
# depend on the platform and the vehicle abstraction to depend on the vehicle interface.
# The configurations are loaded if the PlatformConfig, VehIfcConfig and VehAbstrConfig keywords
# are present and describe a valid configuration file.
# A relative path is relative to the installation directory (being "exe_location/instance_id").
#
# Example:
# PlatformConfig = "platform.toml"
# VehIfcConfig = "vehicle_ifc.toml"
# VehAbstrConfig = "vehicle_abstract.toml"
#
PlatformConfig = ""
VehIfcConfig = ""
VehAbstrConfig = ""
# 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"
AppConfig = ""
)toml";
// If the template is not existing, create a default settings file...
std::string ssSettings;
bool bChangeDetected = false;
if (!std::filesystem::exists(m_pathInstallDir / "settings.toml"))
{
bChangeDetected = true;
ssSettings = std::move(ssSettingsTemplate);
m_bPlatformConfig = true;
m_bVehIfcConfig = true;
m_bVehAbstrConfig = true;
m_bUserConfig = true;
}
else
{
// Open the existing settings file
std::ifstream fstream(m_pathInstallDir / "settings.toml");
if (!fstream.is_open())
{
if (!m_bSilent)
std::cerr << "ERROR: Cannot open the application settings file." << std::endl;
return false;
}
// Read the settings file
ssSettings = std::string((std::istreambuf_iterator<char>(fstream)), std::istreambuf_iterator<char>());
if (ssSettings.empty())
{
if (!m_bSilent)
std::cerr << "ERROR: Cannot read the application settings file; will use default." << std::endl;
ssSettings = std::move(ssSettingsTemplate);
bChangeDetected = true;
}
}
try
{
// Read the settings
toml_parser::CParser parserSettings(ssSettings);
// Check for the version
sdv::toml::CNodeCollection tableRoot(&parserSettings.Root());
if (!tableRoot)
{
if (!m_bSilent)
std::cerr << "ERROR: Invalid TOML file '" << (m_pathInstallDir / "settings.toml").generic_u8string() << "'"
<<
std::endl;
return false;
}
sdv::toml::CNodeCollection tableSettings = tableRoot.GetDirect("Settings");
if (!tableSettings)
tableSettings = tableRoot.AddTable("Settings");
if (!tableSettings)
{
if (!m_bSilent)
std::cerr << "ERROR: Invalid 'Settings' table." << std::endl;
return false;
}
uint32_t uiVersion = tableSettings.GetDirect("Version").GetValue();
if (uiVersion != SDVFrameworkInterfaceVersion)
{
if (!m_bSilent)
std::cerr << "ERROR: Invalid version of application settings file (expected version "
<< SDVFrameworkInterfaceVersion << ", but available version " << uiVersion << ")" << std::endl;
return false;
}
// Generic update config file function
auto fnUpdateConfig = [&](const std::string &rssConfigKey, const std::filesystem::path& rpathConfigFile)
{
sdv::toml::CNode nodeUserConfig = tableSettings.GetDirect(rssConfigKey);
if (nodeUserConfig.GetValue().empty())
{
bChangeDetected = true;
if (nodeUserConfig)
nodeUserConfig.Delete();
nodeUserConfig = tableSettings.AddValue(rssConfigKey, rpathConfigFile);
if (!nodeUserConfig)
{
if (!m_bSilent) std::cerr << "ERROR: Cannot insert the \"Settings." << rssConfigKey <<
"\" value; cannot process further." << std::endl;
return false;
}
}
else if (nodeUserConfig.GetValue() != rpathConfigFile)
{
bChangeDetected = true;
if (!nodeUserConfig.SetValue(rpathConfigFile))
{
if (!m_bSilent) std::cerr << "ERROR: Cannot update the \"Settings." << rssConfigKey <<
"\" value; cannot process further." << std::endl;
return false;
}
}
return true;
};
// Update the configuration path values.
if (m_bPlatformConfig && !fnUpdateConfig("PlatformConfig", m_pathPlatformConfig)) return false;
if (m_bVehIfcConfig && !fnUpdateConfig("VehIfcConfig", m_pathVehIfcConfig)) return false;
if (m_bVehAbstrConfig && !fnUpdateConfig("VehAbstrConfig", m_pathVehAbstrConfig)) return false;
if (m_bUserConfig && !fnUpdateConfig("AppConfig", m_pathUserConfig)) return false;
// Save the settings file if needed
if (bChangeDetected)
{
std::ofstream fstream(m_pathInstallDir / "settings.toml", std::ios::trunc);
if (!fstream.is_open())
{
if (!m_bSilent)
std::cerr << "ERROR: Cannot write the application settings file." << std::endl;
return false;
}
fstream << parserSettings.GenerateTOML();
m_bPlatformConfig = false;
m_bVehIfcConfig = false;
m_bVehAbstrConfig = false;
m_bUserConfig = false;
}
}
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;
}
bool CAppSettings::IsMainApplication() const
{
return m_eContextMode == sdv::app::EAppContext::main;
}
bool CAppSettings::IsIsolatedApplication() const
{
return m_eContextMode == sdv::app::EAppContext::isolated;
}
bool CAppSettings::IsStandaloneApplication() const
{
return m_eContextMode == sdv::app::EAppContext::standalone;
}
bool CAppSettings::IsEssentialApplication() const
{
return m_eContextMode == sdv::app::EAppContext::essential;
}
bool CAppSettings::IsMaintenanceApplication() const
{
return m_eContextMode == sdv::app::EAppContext::maintenance;
}
bool CAppSettings::IsExternalApplication() const
{
return m_eContextMode == sdv::app::EAppContext::external;
}
sdv::app::EAppContext CAppSettings::GetContextType() const
{
return m_eContextMode;
}
uint32_t CAppSettings::GetInstanceID() const
{
return m_uiInstanceID;
}
uint32_t CAppSettings::GetRetries() const
{
return m_uiRetries;
}
std::string CAppSettings::GetLoggerClass() const
{
return m_ssLoggerClass;
}
std::filesystem::path CAppSettings::GetLoggerModulePath() const
{
return m_pathLoggerModule;
}
std::string CAppSettings::GetLoggerProgramTag() const
{
return m_ssProgramTag;
}
sdv::core::ELogSeverity CAppSettings::GetLoggerSeverityFilter() const
{
return m_eSeverityFilter;
}
sdv::core::ELogSeverity CAppSettings::GetConsoleSeverityFilter() const
{
return m_eSeverityViewFilter;
}
bool CAppSettings::IsConsoleSilent() const
{
return m_bSilent;
}
bool CAppSettings::IsConsoleVerbose() const
{
return m_bVerbose;
}
std::filesystem::path CAppSettings::GetRootDir() const
{
return m_pathRootDir;
}
std::filesystem::path CAppSettings::GetInstallDir() const
{
return m_pathInstallDir;
}
std::vector<std::filesystem::path> CAppSettings::GetSystemConfigPaths() const
{
std::vector<std::filesystem::path> vecSysConfigs;
if (!m_pathPlatformConfig.empty())
vecSysConfigs.push_back(m_pathPlatformConfig);
if (!m_pathVehIfcConfig.empty())
vecSysConfigs.push_back(m_pathVehIfcConfig);
if (!m_pathVehAbstrConfig.empty())
vecSysConfigs.push_back(m_pathVehAbstrConfig);
return vecSysConfigs;
}
std::filesystem::path CAppSettings::GetConfigPath(EConfigType eType) const
{
// Is running as main application?
if (!IsMainApplication() && !IsMaintenanceApplication()) return {};
switch (eType)
{
case EConfigType::platform_config:
if (!m_pathPlatformConfig.empty()) return m_pathPlatformConfig;
return "platform.toml";
case EConfigType::vehicle_interface_config:
if (!m_pathVehIfcConfig.empty()) return m_pathVehIfcConfig;
return "vehicle_ifc.toml";
case EConfigType::vehicle_abstraction_config:
if (!m_pathVehAbstrConfig.empty()) return m_pathVehAbstrConfig;
return "vehicle_abstract.toml";
case EConfigType::user_config:
if (!m_pathUserConfig.empty()) return m_pathUserConfig;
return "app_config.toml";
default:
return {};
}
}
bool CAppSettings::EnableConfig(EConfigType eType)
{
// Is running as main application?
if (!IsMainApplication() && !IsMaintenanceApplication()) return false;
switch (eType)
{
case EConfigType::platform_config:
m_bPlatformConfig = true;
if (!m_pathPlatformConfig.empty()) return true;
m_pathPlatformConfig = "platform.toml";
break;
case EConfigType::vehicle_interface_config:
m_bVehIfcConfig = true;
if (!m_pathVehIfcConfig.empty()) return true;
m_pathVehIfcConfig = "vehicle_ifc.toml";
break;
case EConfigType::vehicle_abstraction_config:
m_bVehAbstrConfig = true;
if (!m_pathVehAbstrConfig.empty()) return true;
m_pathVehAbstrConfig = "vehicle_abstract.toml";
break;
case EConfigType::user_config:
m_bUserConfig = true;
if (!m_pathUserConfig.empty()) return true;
m_pathUserConfig = "app_config.toml";
break;
default:
return false;
}
return true;
}
bool CAppSettings::DisableConfig(EConfigType eType)
{
// Is running as main application?
if (!IsMainApplication() && !IsMaintenanceApplication()) return false;
switch (eType)
{
case EConfigType::platform_config:
m_pathPlatformConfig.clear();
m_bPlatformConfig = true;
break;
case EConfigType::vehicle_interface_config:
m_pathVehIfcConfig.clear();
m_bVehIfcConfig = true;
break;
case EConfigType::vehicle_abstraction_config:
m_pathVehAbstrConfig.clear();
m_bVehAbstrConfig = true;
break;
case EConfigType::user_config:
m_pathUserConfig.clear();
m_bUserConfig = true;
break;
default:
return false;
}
return true;
}
std::filesystem::path CAppSettings::GetUserConfigPath() const
{
return m_pathUserConfig;
}
bool CAppSettings::SetUserConfigPath(const std::filesystem::path& rpathConfig)
{
// Is running as server application? Then the user configuration is limited to a filename only.
if (IsMainApplication() || IsMaintenanceApplication() || IsIsolatedApplication())
{
// Must be a filename only.
if (!rpathConfig.has_filename() || rpathConfig.has_parent_path())
return false;
}
// Assign the path
m_pathUserConfig = rpathConfig;
return true;
}
bool CAppSettings::RemoveUserConfigPath()
{
// Is running as main application?
if (!IsMainApplication()) return false;
// Clear the path
m_pathUserConfig.clear();
return true;
}
sdv::sequence<sdv::u8string> CAppSettings::GetNames() const
{
sdv::sequence<sdv::u8string> seqNames = {"app.instance_id", "console.info_level"};
return seqNames;
}
sdv::any_t CAppSettings::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 CAppSettings::Set(/*in*/ const sdv::u8string& /*ssAttribute*/, /*in*/ sdv::any_t /*anyAttribute*/)
{
// Currently there are not setting attributes...
return false;
}
uint32_t CAppSettings::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;
}
void CAppSettings::Reset()
{
m_eContextMode = sdv::app::EAppContext::no_context;
m_pathLoggerModule.clear();
m_ssLoggerClass.clear();
m_ssProgramTag.clear();
m_eSeverityFilter = sdv::core::ELogSeverity::info;
m_eSeverityViewFilter = sdv::core::ELogSeverity::error;
m_uiInstanceID = 0u;
m_bSilent = false;
m_bVerbose = false;
m_pathRootDir.clear();
m_pathInstallDir.clear();
m_pathPlatformConfig.clear();
m_pathVehIfcConfig.clear();
m_pathVehAbstrConfig.clear();
m_pathUserConfig.clear();
m_bPlatformConfig = false;
m_bVehIfcConfig = false;
m_bVehAbstrConfig = false;
m_bUserConfig = false;
}
CAppSettings& CAppSettingsService::GetAppSettings()
{
return ::GetAppSettings();
}

View File

@@ -0,0 +1,417 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef APP_SETTINGS_H
#define APP_SETTINGS_H
#include <interfaces/app.h>
#include <support/component_impl.h>
/**
* @brief Application settings class.
* @details The application settings class is responsible for interpretation of the initial application startup configuration and
* reading and writing the application main settings.
* The startup configuration is a string using 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 (only for local applications).
* 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).
* @todo Add dedicated config (rather than standard config) as startup param.
*
* The settings file is a TOML file with the following structure:
* @code
* [Settings]
* Version = 100
*
* # The system configuration is divided into:
* # 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 vehicle abstraction services
* # The configuration files are loaded exactly in that order, allowing the vehicle interface to
* # depend on the platform and the vehicle abstraction to depend on the vehicle interface.
* # The configurations are loaded if the PlatformConfig, VehIfcConfig and VehAbstrConfig keywords
* # are present and describe a valid configuration file.
* # A relative path is relative to the installation directory (being "exe_location/instance_id").
* #
* # Example:
* # PlatformConfig = "platform.toml"
* # VehIfcConfig = "vehicle_ifc.toml"
* # VehAbstrConfig = "vehicle_abstract.toml"
* #
* PlatformConfig = ""
* VehIfcConfig = ""
* VehAbstrConfig = ""
*
* # 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"
* AppConfig = ""
* @endcode
*/
class CAppSettings : public sdv::IInterfaceAccess, public sdv::app::IAppContext, public sdv::IAttributes
{
public:
/**
* @brief Constructor
*/
CAppSettings();
/**
* @brief Destructor
*/
~CAppSettings();
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::app::IAppContext)
END_SDV_INTERFACE_MAP()
/**
* @brief Process the application starrtup configuration.
* @param[in] rssConfig Reference to the configuration content (TOML format).
* @return Returns 'true' when processing was successful; false when not.
*/
bool ProcessAppStartupConfig(const sdv::u8string& rssConfig);
/**
* @brief Load the application settings file.
* @attention Only works if the application is running in main, isolation or maintenance mode.
* @remarks When there is no settings file, this is not an error. Default settings will be assumed.
* @return Returns whether the loading was successful.
*/
bool LoadSettingsFile();
/**
* @brief Save the application settings file (or create when not existing yet).
* @attention Only works if the application is running in main, isolation or maintenance mode.
* @return Returns whether the saving was successful.
*/
bool SaveSettingsFile();
/**
* @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.
* @details Get the instance. If not otherwise specified, the current instance depends on whether the application is running
* as main or isolated application, in which case the instance is 1000. In all other cases the instance is 0. An instance
* ID can be supplied through the app startup configuration.
* @return The core instance ID.
*/
uint32_t GetInstanceID() const override;
/**
* @brief Return the number of retries to establish a connection. Overload of sdv::app::IAppContext::GetRetries.
* @return Number of retries.
*/
uint32_t GetRetries() const override;
/**
* @brief Get the class name of a logger service, if specified in the application startup configuration.
* @return The logger class name.
*/
std::string GetLoggerClass() const;
/**
* @brief Get the logger service module path, if specified in the application startup configuration.
* @return The logger module path.
*/
std::filesystem::path GetLoggerModulePath() const;
/**
* @brief Get the logger program tag, if specified in the application startup configuration.
* @return The logger program tag.
*/
std::string GetLoggerProgramTag() const;
/**
* @brief Get the logger severity filter, if specified in the application startup configuration.
* @return The logger severity filter.
*/
sdv::core::ELogSeverity GetLoggerSeverityFilter() const;
/**
* @brief Get the console reporting severity file, if specified in the application startup configuration.
* @return The console reporting severity filter value.
*/
sdv::core::ELogSeverity GetConsoleSeverityFilter() const;
/**
* @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;
/**
* @brief Get the root directory for the application.
* @remarks Is only valid when used in main, isolated and maintenance applications.
* @return The location of root directory.
*/
std::filesystem::path GetRootDir() const;
/**
* @brief Get the installation directory of user components (root directory / instance ID).
* @remarks Is only valid when used in main, isolated and maintenance applications.
* @return The location of the installation director.
*/
std::filesystem::path GetInstallDir() const;
/**
* @brief Get a vector with the system configuration paths (relative to the installation directory) as specified in the
* settings file.
* @return The vector of system configuration paths.
*/
std::vector<std::filesystem::path> GetSystemConfigPaths() const;
/**
* @brief Configuration type
*/
enum class EConfigType
{
platform_config, ///< Contains the platform configuration
vehicle_interface_config, ///< Contains the vehicle interface configuration
vehicle_abstraction_config, ///< Contains the vehicle abstraction configuration
user_config ///< Contains the user configuration
};
/**
* @brief Get the stored or default configuration path name.
* @attention Setting a path is only valid when running as main application.
* @return The path name dependent on the configuration type. If no path name was configured, the default path name is returned.
*/
std::filesystem::path GetConfigPath(EConfigType eType) const;
/**
* @brief Enable a configuration file in the application settings.
* @attention The configuration file needs to be located at the root directory of the instance installation.
* @attention Setting a path is only valid when running as main application.
* @param[in] eType The configuration type to set the path for.
* @return Returns 'true' when adding the config file path was successful (or when the path already exists in the settings);
* otherwise returns 'false'.
*/
bool EnableConfig(EConfigType eType);
/**
* @brief Disable and remove a configuration file from the application settings.
* @attention The configuration file needs to be located at the root directory of the instance installation.
* @attention Removing a psth is only valid when running as main application.
* @param[in] eType The configuration type to remove the path from.
* @return Returns 'true' when the removal was successful.
*/
bool DisableConfig(EConfigType eType);
/**
* @brief Get the path to the user copnfiguration (relative to the installation directory) as specified in the settings file.
* @return Path to the user configuration.
*/
std::filesystem::path GetUserConfigPath() const;
/**
* @brief Set the user configuration file into the application settings. If a user configuration file is stored already in the
* settings file, the configuration file name is replaced by the new configuration file name.
* @remarks Setting the user config path is only valid for local applications. For server application, the path is managed by
* enabling the configuration.
* @param[in] rpathConfig Reference to the path containing the configuration file name.
* @return Returns 'true' when setting the config file path was successful; otherwise returns 'false'.
*/
bool SetUserConfigPath(const std::filesystem::path& rpathConfig);
/**
* @brief Remove the user configuration file from the application settings.
* @attention Removing the path is only valid when running as main application.
* @return Returns 'true' when the removal was successful.
*/
bool RemoveUserConfigPath();
/**
* @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 Reset the settings after a shutdown.
*/
void Reset();
private:
sdv::app::EAppContext m_eContextMode = sdv::app::EAppContext::no_context; ///< The application is running as...
uint32_t m_uiInstanceID = 0u; ///< Instance number.
uint32_t m_uiRetries = 0u; ///< Number of retries to establish a connection.
std::string m_ssLoggerClass; ///< Class name of a logger service.
std::filesystem::path m_pathLoggerModule; ///< Module name of a custom logger.
std::string m_ssProgramTag; ///< Program tag to use when logging.
sdv::core::ELogSeverity m_eSeverityFilter = sdv::core::ELogSeverity::info; ///< Severity level filter while logging.
sdv::core::ELogSeverity m_eSeverityViewFilter = sdv::core::ELogSeverity::error; ///< Severity level filter while logging.
bool m_bSilent = false; ///< When set, no console reporting takes place.
bool m_bVerbose = false; ///< When set, extensive console reporting takes place.
std::filesystem::path m_pathRootDir; ///< Location of user component root directory.
std::filesystem::path m_pathInstallDir; ///< Location of user component installations (root with instance).
std::filesystem::path m_pathPlatformConfig; ///< The platform configuration from the settings file.
std::filesystem::path m_pathVehIfcConfig; ///< The vehicle interface configuration from the settings file.
std::filesystem::path m_pathVehAbstrConfig; ///< The vehicle abstraction configuration from the settings file.
std::filesystem::path m_pathUserConfig; ///< The user configuration from the settings file.
bool m_bPlatformConfig = false; ///< Platform config was explicitly enabled/disabled.
bool m_bVehIfcConfig = false; ///< Vehicle interface config was explicitly enabled/disabled.
bool m_bVehAbstrConfig = false; ///< Vehicle abstraction config was explicitly enabled/disabled.
bool m_bUserConfig = false; ///< User config was explicitly enabled/disabled.
};
/**
* @brief Return the application settings class.
* @return Reference to the application settings.
*/
CAppSettings& GetAppSettings();
/**
* @brief App settings service class.
*/
class CAppSettingsService : public sdv::CSdvObject
{
public:
CAppSettingsService() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY_MEMBER(sdv::IAttributes, GetAppSettings())
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("AppSettingsService")
DECLARE_OBJECT_SINGLETON()
/**
* @brief Get access to the application settings.
* @return Returns the one global instance of the application config.
*/
static CAppSettings& GetAppSettings();
};
DEFINE_SDV_OBJECT(CAppSettingsService)
#endif // !defined APP_SETTINGS_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "installation_composer.h"
#include <ctime>
#include <fstream>
@@ -59,18 +72,18 @@ std::vector<std::filesystem::path> CInstallComposer::AddModule(const std::filesy
}
else
{
// If a base path is supplied, the path needs to be absolute.
if (!rpathBasePath.is_absolute() || !std::filesystem::exists(rpathBasePath) || !std::filesystem::is_directory(rpathBasePath))
// The base path needs to be absolute.
if (!rpathBasePath.is_absolute() || !std::filesystem::exists(rpathBasePath) ||
!std::filesystem::is_directory(rpathBasePath))
{
sdv::XInvalidPath exception;
exception.ssPath = rpathRelTargetDir.generic_u8string();
exception.ssPath = rpathBasePath.generic_u8string();
throw exception;
}
}
// Check for the module path
std::string ssModulePathCopy = rssModulePath;
if (ssModulePathCopy.empty())
if (rssModulePath.empty())
{
// Base path must be present
if (rpathBasePath.empty())
@@ -114,7 +127,7 @@ std::vector<std::filesystem::path> CInstallComposer::AddModule(const std::filesy
}
// Get the list of files
auto vecFiles = CollectWildcardPath(rpathBasePath, ssModulePathCopy, eAlgorithm);
auto vecFiles = CollectWildcardPath(rpathBasePath, rssModulePath, eAlgorithm);
// For each file, check whether the file is somewhere within the base path (if provided) and add the file to the module list.
for (const std::filesystem::path& rpathFile : vecFiles)
@@ -166,6 +179,8 @@ sdv::pointer<uint8_t> CInstallComposer::Compose(const std::string& rssInstallNam
// Installation manifest
CInstallManifest manifest;
// cppcheck warns that the following condition is always true. This is incorrect. Suppress the warning.
// cppcheck-suppress knownConditionTrueFalse
if (!manifest.Create(rssInstallName))
{
sdv::installation::XFailedManifestCreation exception;
@@ -228,6 +243,8 @@ bool CInstallComposer::Compose(const std::filesystem::path& rpathPackage, const
// Installation manifest
CInstallManifest manifest;
// cppcheck warns that the following condition is always true. This is incorrect. Suppress the warning.
// cppcheck-suppress knownConditionTrueFalse
if (!manifest.Create(rssInstallName))
{
sdv::installation::XFailedManifestCreation exception;
@@ -365,6 +382,8 @@ CInstallManifest CInstallComposer::ComposeDirect(const std::string& rssInstallNa
// CReate the installation manifest
CInstallManifest manifest;
// cppcheck warns that the following condition is always true. This is incorrect. Suppress the warning.
// cppcheck-suppress knownConditionTrueFalse
if (!manifest.Create(rssInstallName))
{
sdv::installation::XFailedManifestCreation exception;
@@ -459,6 +478,8 @@ CInstallManifest CInstallComposer::ComposeInstallManifest(const std::string& rss
// Installation manifest
CInstallManifest manifest;
// cppcheck warns that the following condition is always true. This is incorrect. Suppress the warning.
// cppcheck-suppress knownConditionTrueFalse
if (!manifest.Create(rssInstallName))
{
sdv::installation::XFailedManifestCreation exception;

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef INSTALL_PACKAGING_H
#define INSTALL_PACKAGING_H

View File

@@ -1,6 +1,20 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "installation_manifest.h"
#include "toml_parser/parser_toml.h"
#include <support/component_impl.h>
#include <support/serdes.h>
#include <support/toml.h>
#if defined _WIN32 && defined __GNUC__
#pragma GCC diagnostic push
@@ -11,6 +25,74 @@
#include <dlfcn.h>
#endif
std::string ClassInfo2TOML(const sdv::SClassInfo& rsClass)
{
toml_parser::CParser parser("");
sdv::toml::CNodeCollection nodeRoot(&parser.Root());
auto nodeClass = nodeRoot.AddTableArray("Class");
if (!rsClass.ssModulePath.empty())
nodeClass.AddValue("Path", rsClass.ssModulePath);
if (rsClass.ssName.empty()) return {};
nodeClass.AddValue("Name", rsClass.ssName);
if (!rsClass.seqClassAliases.empty())
{
auto nodeAliases = nodeClass.AddArray("Aliases");
for (const auto& rssAlias : rsClass.seqClassAliases)
nodeAliases.AddValue("", rssAlias);
}
if (!rsClass.ssDefaultObjectName.empty() && rsClass.ssDefaultObjectName != rsClass.ssName)
nodeClass.AddValue("DefaultName", rsClass.ssDefaultObjectName);
if (!rsClass.ssDefaultConfig.empty())
nodeClass.AddTOML(rsClass.ssDefaultConfig);
if (rsClass.eType == sdv::EObjectType::undefined) return {};
nodeClass.AddValue("Type", ::sdv::ObjectType2String(rsClass.eType));
if (rsClass.uiFlags & static_cast<uint32_t>(sdv::EObjectFlags::singleton))
nodeClass.AddValue("Singleton", true);
if (!rsClass.seqDependencies.empty())
{
auto nodeDependencies = nodeClass.AddArray("Dependencies");
for (const auto& rssDependency : rsClass.seqDependencies)
nodeDependencies.AddValue("" , rssDependency);
}
return parser.GenerateTOML();
}
sdv::SClassInfo TOML2ClassInfo(const std::string& rssTOML, size_t nIndex /*= 0*/)
{
toml_parser::CParser parser(rssTOML);
return TOML2ClassInfo(parser.Root().Cast<toml_parser::CNodeCollection>(), nIndex);
}
sdv::SClassInfo TOML2ClassInfo(const std::shared_ptr<toml_parser::CNodeCollection>& rptrTOML, size_t nIndex /*= 0*/)
{
if (!rptrTOML) return {};
sdv::toml::CNodeCollection nodeRoot(rptrTOML.get());
sdv::toml::CNodeCollection nodeClass = nodeRoot.GetDirect("Class[" + std::to_string(nIndex) + "]");
if (!nodeClass)
nodeClass = nodeRoot.GetDirect("Class");
if (!nodeClass) return {};
sdv::SClassInfo sClass{};
sClass.ssModulePath = nodeClass.GetDirect("Path").GetValueAsString();
sClass.ssName = nodeClass.GetDirect("Name").GetValueAsString();
sdv::toml::CNodeCollection nodeAliases = nodeClass.GetDirect("Aliases");
for (size_t nAliasIndex = 0; nAliasIndex < nodeAliases.GetCount(); nAliasIndex++)
sClass.seqClassAliases.push_back(nodeAliases.Get(nAliasIndex).GetValueAsString());
sClass.ssDefaultObjectName = nodeClass.GetDirect("DefaultName").GetValueAsString();
sClass.ssDefaultConfig = nodeClass.GetDirect("Parameters").GetTOML();
sClass.eType = sdv::String2ObjectType(nodeClass.GetDirect("Type").GetValue());
if (static_cast<bool>(nodeClass.GetDirect("Singleton").GetValue()))
sClass.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
sdv::toml::CNodeCollection nodeDependencies = nodeClass.GetDirect("Dependencies");
for (size_t nDependencyIndex = 0; nDependencyIndex < nodeDependencies.GetCount(); nDependencyIndex++)
sClass.seqDependencies.push_back(nodeDependencies.Get(nDependencyIndex).GetValueAsString());
if (sClass.ssName.empty() || sClass.eType == sdv::EObjectType::undefined)
return {};
return sClass;
}
/**
* @brief Read the module manifest from the binary.
* @param[in] rpathModule Reference to the module to read the manifest from.
@@ -165,61 +247,43 @@ bool CInstallManifest::Read(const std::string& rssManifest, bool bBlockSystemObj
// Parse the manifest
toml_parser::CParser parser(rssManifest);
sdv::toml::CNodeCollection nodeRoot(&parser.Root());
// Get the installation version - must be identical to the interface version
auto ptrInstallVersionNode = parser.Root().Direct("Installation.Version");
if (!ptrInstallVersionNode || ptrInstallVersionNode->GetValue() != SDVFrameworkInterfaceVersion) return false;
if (nodeRoot.GetDirect("Installation.Version").GetValue() != SDVFrameworkInterfaceVersion) return false;
// Get the installation name
auto ptrInstallNameNode = parser.Root().Direct("Installation.Name");
if (!ptrInstallNameNode) return false;
m_ssInstallName = static_cast<std::string>(ptrInstallNameNode->GetValue());
m_ssInstallName = nodeRoot.GetDirect("Installation.Name").GetValueAsString();
if (m_ssInstallName.empty()) return false;
// Get installation properties. The properties are optional
auto ptrProperties = parser.Root().Direct("Properties");
std::shared_ptr<toml_parser::CTable> ptrPropertyTable;
if (ptrProperties) ptrPropertyTable = ptrProperties->Cast<toml_parser::CTable>();
if (ptrPropertyTable)
sdv::toml::CNodeCollection nodeProperties = nodeRoot.GetDirect("Properties");
for (size_t nIndex = 0; nIndex < nodeProperties.GetCount(); nIndex++)
{
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());
}
auto nodeProperty = nodeProperties.Get(nIndex);
if (nodeProperty)
m_mapProperties[nodeProperty.GetName()] = nodeProperty.GetValueAsString();
}
// Build the module list
auto ptrModulesNode = parser.Root().Direct("Module");
if (!ptrModulesNode) return true; // No modules in the manifest
auto ptrModuleArrayNode = ptrModulesNode->Cast<toml_parser::CArray>();
if (!ptrModuleArrayNode) return false; // Must be array
for (uint32_t uiModuleIndex = 0; uiModuleIndex < ptrModuleArrayNode->GetCount(); uiModuleIndex++)
sdv::toml::CNodeCollection nodeModules = nodeRoot.GetDirect("Module");
if (!nodeModules) return true; // No modules in the manifest
for (size_t nIndex = 0; nIndex < nodeModules.GetCount(); nIndex++)
{
// Get the module
auto ptrModule = ptrModuleArrayNode->Get(uiModuleIndex);
if (!ptrModule) continue;
sdv::toml::CNodeCollection nodeModule = nodeModules.Get(nIndex);
if (!nodeModule) continue;
// Get the module path
auto ptrModulePath = ptrModule->Direct("Path");
if (!ptrModulePath) continue;
std::filesystem::path pathModule = static_cast<std::string>(ptrModulePath->GetValue());
std::filesystem::path pathModule = nodeModule.GetDirect("Path").GetValueAsPath();
std::string ssModuleManifest;
// Get the component list (if available)
auto ptrModuleComponents = ptrModule->Direct("Component");
if (ptrModuleComponents)
{
// The module manifest contains the TOML text of the component array
auto ptrModuleComponentArray = ptrModuleComponents->Cast<toml_parser::CArray>();
if (ptrModuleComponentArray)
ssModuleManifest = ptrModuleComponents->GenerateTOML();
}
// Get the class list (if available) and get the fitting TOML for the classes.
sdv::toml::CNodeCollection nodeClasses = nodeModule.GetDirect("Class");
if (nodeClasses) ssModuleManifest = nodeClasses.GetTOML();
// Add the module
m_vecModules.push_back(SModule(pathModule, ssModuleManifest,
m_bBlockSystemObjects));
m_vecModules.push_back(SModule(pathModule, ssModuleManifest, m_bBlockSystemObjects));
}
return true;
@@ -290,7 +354,7 @@ bool CInstallManifest::AddModule(const std::filesystem::path& rpathModulePath,
auto ptrInterfaceNode = parser.Root().Direct("Interface.Version");
if (!ptrInterfaceNode) return false;
if (ptrInterfaceNode->GetValue() != SDVFrameworkInterfaceVersion) return false;
auto ptrComponentsNode = parser.Root().Direct("Component");
auto ptrComponentsNode = parser.Root().Direct("Class");
if (!ptrComponentsNode) return true; // No component available in the manifest
if (!ptrComponentsNode->Cast<toml_parser::CArray>()) return false;
ssComponentsManifest = ptrComponentsNode->GenerateTOML();
@@ -330,39 +394,39 @@ std::string CInstallManifest::FindModuleManifest(const std::filesystem::path& rp
return {};
}
std::optional<CInstallManifest::SComponent> CInstallManifest::FindComponentByClass(const std::string& rssClass) const
std::optional<sdv::SClassInfo> CInstallManifest::FindComponentByClass(const std::string& rssClass) const
{
// Search for the correct module
SComponent sRet{};
sdv::SClassInfo 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)
return std::find_if(rsEntry.vecClasses.begin(), rsEntry.vecClasses.end(),
[&](const sdv::SClassInfo& sClass)
{
// 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())
if (sClass.ssName == rssClass ||
std::find(sClass.seqClassAliases.begin(), sClass.seqClassAliases.end(), rssClass) !=
sClass.seqClassAliases.end())
{
sRet = sComponent;
sRet = sClass;
return true;
}
return false;
}) != rsEntry.vecComponents.end();
}) != rsEntry.vecClasses.end();
});
if (itModule != m_vecModules.end()) return sRet;
return {};
}
std::vector<CInstallManifest::SComponent> CInstallManifest::ComponentList() const
std::vector<sdv::SClassInfo> CInstallManifest::ClassList() const
{
std::vector<SComponent> vecComponents;
std::vector<sdv::SClassInfo> vecClasses;
for (const auto& rsModule : m_vecModules)
{
for (const auto& rsComponent : rsModule.vecComponents)
vecComponents.push_back(rsComponent);
for (const auto& rsComponent : rsModule.vecClasses)
vecClasses.push_back(rsComponent);
}
return vecComponents;
return vecClasses;
}
std::vector<std::filesystem::path> CInstallManifest::ModuleList() const
@@ -409,67 +473,65 @@ CInstallManifest::SModule::SModule(const std::filesystem::path& rpathRelModule,
{
// Parse the manifest and extract information from them...
toml_parser::CParser parser(rssManifest);
auto ptrComponents = parser.Root().Direct("Component");
if (!ptrComponents) return; // No objects...
auto ptrComponentArray = ptrComponents->Cast<toml_parser::CArray>();
if (!ptrComponentArray) return; // No objects...
for (uint32_t uiIndex = 0; uiIndex < ptrComponentArray->GetCount(); uiIndex++)
auto ptrClasses = parser.Root().Direct("Class");
if (!ptrClasses) return; // No objects...
auto ptrClassArray = ptrClasses->Cast<toml_parser::CArray>();
if (!ptrClassArray) return; // No objects...
for (uint32_t uiIndex = 0; uiIndex < ptrClassArray->GetCount(); uiIndex++)
{
auto ptrComponent = ptrComponentArray->Get(uiIndex);
if (!ptrComponent) continue;
//auto ptrClass = ptrClassArray->Get(uiIndex);
//if (!ptrClass) continue;
// Fill in the component structure
SComponent sComponent{};
//sComponent.pathModule = rpathModule;
sComponent.pathRelModule = rpathRelModule;
sComponent.ssManifest = ptrComponent->GenerateTOML(toml_parser::CGenContext("Component"));
auto ptrClassName = ptrComponent->Direct("Class");
if (!ptrClassName) continue;
sComponent.ssClassName = static_cast<std::string>(ptrClassName->GetValue());
auto ptrAliases = ptrComponent->Direct("Aliases");
if (ptrAliases)
{
auto ptrAliasesArray = ptrAliases->Cast<toml_parser::CArray>();
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->Direct("DefaultName");
if (ptrDefaultName) sComponent.ssDefaultObjectName = static_cast<std::string>(ptrDefaultName->GetValue());
else sComponent.ssDefaultObjectName = sComponent.ssClassName;
auto ptrType = ptrComponent->Direct("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->Direct("Singleton");
if (ptrSingleton && static_cast<bool>(ptrSingleton->GetValue()))
sComponent.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
auto ptrDependencies = ptrComponent->Direct("Dependencies");
if (ptrDependencies)
{
auto ptrDependencyArray = ptrDependencies->Cast<toml_parser::CArray>();
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()));
}
}
//// Fill in the component structure
//sdv::SClassInfo sClass{};
////sClass.pathModule = rpathModule;
//sClass.ssModulePath = rpathRelModule.generic_u8string();
////sClass.ssManifest = ptrClass->GenerateTOML(toml_parser::CGenContext("Class"));
//auto ptrClassName = ptrClass->Direct("Name");
//if (!ptrClassName) continue;
//sClass.ssName = ptrClassName->GetValue();
//auto ptrAliases = ptrClass->Direct("Aliases");
//if (ptrAliases)
//{
// auto ptrAliasesArray = ptrAliases->Cast<toml_parser::CArray>();
// for (uint32_t uiAliasIndex = 0; ptrAliasesArray && uiAliasIndex < ptrAliasesArray->GetCount(); uiAliasIndex++)
// {
// auto ptrClassAlias = ptrAliasesArray->Get(uiAliasIndex);
// if (ptrClassAlias)
// sClass.seqClassAliases.push_back(static_cast<sdv::u8string>(ptrClassAlias->GetValue()));
// }
//}
//auto ptrDefaultName = ptrClass->Direct("DefaultName");
//if (ptrDefaultName) sClass.ssDefaultObjectName = ptrDefaultName->GetValue();
//else sClass.ssDefaultObjectName = sClass.ssName;
//auto ptrType = ptrClass->Direct("Type");
//if (!ptrType) continue;
//sClass.eType = sdv::String2ObjectType(ptrType->GetValue());
//if (sClass.eType == sdv::EObjectType::Undefined) continue;
//if (bBlockSystemObjects && sClass.eType == sdv::EObjectType::system_object) continue;
//auto ptrSingleton = ptrClass->Direct("Singleton");
//if (ptrSingleton && static_cast<bool>(ptrSingleton->GetValue()))
// sClass.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
//auto ptrDependencies = ptrClass->Direct("Dependencies");
//if (ptrDependencies)
//{
// auto ptrDependencyArray = ptrDependencies->Cast<toml_parser::CArray>();
// for (uint32_t uiDependencyIndex = 0; ptrDependencyArray && uiDependencyIndex < ptrDependencyArray->GetCount();
// uiDependencyIndex++)
// {
// auto ptrDependsOn = ptrDependencyArray->Get(uiDependencyIndex);
// if (ptrDependsOn)
// sClass.seqDependencies.push_back(static_cast<sdv::u8string>(ptrDependsOn->GetValue()));
// }
//}
vecComponents.push_back(sComponent);
//vecClasses.push_back(sClass);
auto sClass = TOML2ClassInfo(parser.Root().Cast<toml_parser::CNodeCollection>(), uiIndex);
if (bBlockSystemObjects && sClass.eType == sdv::EObjectType::system_object) continue;
if (sClass.eType == sdv::EObjectType::undefined) continue;
sClass.ssModulePath = rpathRelModule.generic_u8string();
vecClasses.push_back(sClass);
}
}

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef INSTALL_MANIFEST_H
#define INSTALL_MANIFEST_H
@@ -7,9 +20,37 @@
#include <optional>
#include <interfaces/core.h>
#include <interfaces/config.h>
#include "toml_parser/parser_toml.h"
#include <map>
#include <cstdlib>
/**
* @brief Create a TOML string using the class information.
* @param[in] rsClass Reference to the class structure.
* @return Returns a string to the class table rray entry or an empty string when the type of class name were invalid or empty.
*/
std::string ClassInfo2TOML(const sdv::SClassInfo& rsClass);
/**
* @brief Extract the class information from a TOML class configuration.
* @remarks The class information could be stored as a table array entry (then the index is used to extract the information). It
* also could be a table. Then the index parameter is ignored.
* @param[in] rssTOML Reference to the string containing the TOML class configuration.
* @param[in] nIndex The index in the table array to provide the class information for.
* @return Returns the class information or an empty class structure when no information is available.
*/
sdv::SClassInfo TOML2ClassInfo(const std::string& rssTOML, size_t nIndex = 0);
/**
* @brief Extract the class information from a TOML class configuration.
* @remarks The class information could be stored as a table array entry (then the index is used to extract the information). It
* also could be a table. Then the index parameter is ignored.
* @param[in] rptrTOML Reference to the smart pointer holding the TOML class configuration.
* @param[in] nIndex The index in the table array to provide the class information for.
* @return Returns the class information or an empty class structure when no information is available.
*/
sdv::SClassInfo TOML2ClassInfo(const std::shared_ptr<toml_parser::CNodeCollection>& rptrTOML, size_t nIndex = 0);
/**
* @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
@@ -87,7 +128,7 @@ inline bool RefersToRelativeParent(const std::filesystem::path& rpathRelative)
* [[Module]]
* Path = "mallard.sdv # Relative path to the module
*
* [[Module.Component]] # Component manifest
* [[Module.Class]] # 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
@@ -108,21 +149,6 @@ inline bool RefersToRelativeParent(const std::filesystem::path& rpathRelative)
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.
*/
@@ -224,13 +250,13 @@ public:
* @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;
std::optional<sdv::SClassInfo> FindComponentByClass(const std::string& rssClass) const;
/**
* @brief Get a vector of all components stored in this installation manifest.
* @return The component manifest vector.
* @brief Get a vector of all component classes stored in this installation manifest.
* @return The component class list vector.
*/
std::vector<SComponent> ComponentList() const;
std::vector<sdv::SClassInfo> ClassList() const;
/**
* @brief Get the module list.
@@ -283,9 +309,9 @@ private:
*/
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::filesystem::path pathRelModule; ///< Relative module path (relative to the installation directory).
std::string ssManifest; ///< Manifest containing the component classes.
std::vector<sdv::SClassInfo> vecClasses; ///< Vector with contained component classes.
};
std::string m_ssInstallName; ///< Installation name.

View File

@@ -1,5 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "iso_monitor.h"
#include "sdv_core.h"
#include "app_control.h"
CIsoMonitor::CIsoMonitor(sdv::IInterfaceAccess* pObject) :
@@ -16,16 +28,16 @@ void CIsoMonitor::Initialize(/*in*/ const sdv::u8string& ssObjectConfig)
if (m_pObjectControl)
{
m_pObjectControl->Initialize(ssObjectConfig);
m_eObjectStatus = m_pObjectControl->GetStatus();
m_eObjectState = m_pObjectControl->GetObjectState();
}
else
m_eObjectStatus = sdv::EObjectStatus::initialized;
m_eObjectState = sdv::EObjectState::initialized;
}
sdv::EObjectStatus CIsoMonitor::GetStatus() const
sdv::EObjectState CIsoMonitor::GetObjectState() const
{
if (m_pObjectControl) return m_pObjectControl->GetStatus();
return m_eObjectStatus;
if (m_pObjectControl) return m_pObjectControl->GetObjectState();
return m_eObjectState;
}
void CIsoMonitor::SetOperationMode(/*in*/ sdv::EOperationMode eMode)
@@ -33,15 +45,22 @@ void CIsoMonitor::SetOperationMode(/*in*/ sdv::EOperationMode eMode)
if (m_pObjectControl) m_pObjectControl->SetOperationMode(eMode);
}
sdv::u8string CIsoMonitor::GetObjectConfig() const
{
if (m_pObjectControl)
return m_pObjectControl->GetObjectConfig();
return {};
}
void CIsoMonitor::Shutdown()
{
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
m_eObjectState = sdv::EObjectState::shutdown_in_progress;
if (m_pObjectControl)
{
m_pObjectControl->Shutdown();
m_eObjectStatus = m_pObjectControl->GetStatus();
m_eObjectState = m_pObjectControl->GetObjectState();
}
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
m_eObjectState = sdv::EObjectState::destruction_pending;
GetAppControl().RequestShutdown();
m_pObjectControl = nullptr;
}

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef ISOLATION_OBJECT_MONITOR_H
#define ISOLATION_OBJECT_MONITOR_H
@@ -34,10 +47,10 @@ public:
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.
* @brief Get the current state of the object. Overload of sdv::IObjectControl::GetObjectState.
* @return Return the current state of the object.
*/
virtual sdv::EObjectStatus GetStatus() const override;
virtual sdv::EObjectState GetObjectState() const override;
/**
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
@@ -45,12 +58,18 @@ public:
*/
virtual void SetOperationMode(/*in*/ sdv::EOperationMode eMode) override;
/**
* @brief Get the object configuration for persistence.
* @return The object configuration as TOML string.
*/
virtual sdv::u8string GetObjectConfig() const 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
* Any subsequent call to GetObjectState should return EObjectState::destruction_pending
*/
virtual void Shutdown() override;
@@ -63,7 +82,7 @@ public:
private:
sdv::TInterfaceAccessPtr m_ptrObject; ///< Smart pointer to the object.
sdv::IObjectControl* m_pObjectControl = nullptr; ///< Pointer to the object control of the application
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status (in case there is no object control).
sdv::EObjectState m_eObjectState = sdv::EObjectState::initialization_pending; ///< Object status (in case there is no object control).
};
#endif // !defined ISOLATION_OBJECT_MONITOR_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef LOCAL_SHUTDOWN_REQUEST_H
#define LOCAL_SHUTDOWN_REQUEST_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "log_csv_writer.h"
#include <sstream>

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef LOG_CSV_WRITER_H
#define LOG_CSV_WRITER_H

View File

@@ -1,6 +1,18 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "logger.h"
#include <sstream>
#include "sdv_core.h"
#include "../../global/exec_dir_helper.h"
#ifdef __unix__
@@ -10,6 +22,12 @@
#include <unistd.h> // for getpid
#endif
CLogger& GetDefaultLogger()
{
static CLogger default_logger;
return default_logger;
}
CLogger::~CLogger()
{
#ifdef __unix__
@@ -140,12 +158,3 @@ std::string CLogger::GetDateTime(std::chrono::time_point<std::chrono::system_clo
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

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef LOGGER_H
#define LOGGER_H
@@ -95,7 +108,12 @@ private:
#endif
};
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
/**
* @brief Return the default logger.
* @attention Use the logger control to access the logger.
* @return Reference to the default logger.
*/
CLogger& GetDefaultLogger();
/**
* @brief Logger service
@@ -111,18 +129,11 @@ public:
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
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
DEFINE_SDV_OBJECT(CLoggerService)
#endif // !defined LOGGER_H

View File

@@ -1,9 +1,28 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "logger_control.h"
#ifdef _WIN32
#include <process.h> // Needed for _getpid
#endif
CLoggerControl& GetLoggerControl()
{
static CLoggerControl logger_control;
return logger_control;
}
CLoggerControl::~CLoggerControl()
{
// Reset the logger. This will finialize an open log.

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef LOGGER_CONTROL_H
#define LOGGER_CONTROL_H
@@ -66,4 +79,10 @@ private:
sdv::core::ILogger* m_pLogger = nullptr; ///< Interface for the actual logger.
};
/**
* @brief Return the logger control.
* @return Reference to the logger control.
*/
CLoggerControl& GetLoggerControl();
#endif // !defined LOGGER_CONTROL_H

View File

@@ -1,5 +1,24 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "memory.h"
CMemoryManager& GetMemoryManager()
{
static CMemoryManager memory_mgr;
return memory_mgr;
}
sdv::pointer<uint8_t> CMemoryManager::Allocate(uint32_t uiLength)
{
return sdv::internal::make_ptr<uint8_t>(this, uiLength);

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef MEMORY_H
#define MEMORY_H
@@ -54,4 +67,10 @@ private:
#endif
};
/**
* @brief Return the memory manager.
* @return Reference to the memory manager.
*/
CMemoryManager& GetMemoryManager();
#endif // !define MEMORY_H

View File

@@ -1,8 +1,23 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "module.h"
#include "sdv_core.h"
#include <support/component_impl.h>
#include <support/local_service_access.h>
#include <functional>
#include <algorithm>
#include "toml_parser/parser_toml.h"
#include "repository.h"
#ifdef _WIN32
// Resolve conflict
@@ -56,7 +71,7 @@ std::string CModuleInst::GetDefaultObjectName(const std::string& ssClassName) co
{
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;
return itClass->second.ssDefaultObjectName.empty() ? itClass->second.ssName : itClass->second.ssDefaultObjectName;
}
bool CModuleInst::IsSingleton(const std::string& ssClassName) const
@@ -109,13 +124,13 @@ sdv::core::TModuleID CModuleInst::GetModuleID() const
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;
sdv::core::SModuleInfo sClass{};
sClass.tModuleID = m_tModuleID;
sClass.ssPath = m_pathModule.filename().generic_u8string();
sClass.ssFilename = m_pathModule.filename().generic_u8string();
sClass.uiVersion = m_uiIfcVersion;
sClass.bActive = m_fnActiveObjects ? m_fnActiveObjects() : false;
return sClass;
}
std::optional<sdv::SClassInfo> CModuleInst::GetClassInfo(const std::string& rssClassName) const
@@ -230,23 +245,23 @@ bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept
}
// Get available classes
auto ptrComponents = parser.Root().Direct("Component");
if (!ptrComponents || !ptrComponents->Cast<toml_parser::CArray>())
auto ptrClasses = parser.Root().Direct("Class");
if (!ptrClasses || !ptrClasses->Cast<toml_parser::CArray>())
{
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->Cast<toml_parser::CArray>()->GetCount(); uiIndex++)
for (std::uint32_t uiIndex = 0; uiIndex < ptrClasses->Cast<toml_parser::CArray>()->GetCount(); uiIndex++)
{
// Fill in the class info.
sdv::SClassInfo sInfo{};
auto ptrComponent = ptrComponents->Cast<toml_parser::CArray>()->Get(uiIndex);
if (!ptrComponent) continue;
auto ptrClassName = ptrComponent->Direct("Class");
sdv::SClassInfo sClass{};
auto ptrClass = ptrClasses->Cast<toml_parser::CArray>()->Get(uiIndex);
if (!ptrClass) continue;
auto ptrClassName = ptrClass->Direct("Name");
if (!ptrClassName) continue;
sInfo.ssClassName = static_cast<std::string>(ptrClassName->GetValue());
auto ptrAliases = ptrComponent->Direct("Aliases");
sClass.ssName = static_cast<std::string>(ptrClassName->GetValue());
auto ptrAliases = ptrClass->Direct("Aliases");
if (ptrAliases)
{
auto ptrAliasesArray = ptrAliases->Cast<toml_parser::CArray>();
@@ -254,28 +269,21 @@ bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept
{
auto ptrClassAlias = ptrAliasesArray->Get(uiAliasIndex);
if (ptrClassAlias)
sInfo.seqClassAliases.push_back(static_cast<sdv::u8string>(ptrClassAlias->GetValue()));
sClass.seqClassAliases.push_back(static_cast<sdv::u8string>(ptrClassAlias->GetValue()));
}
}
auto ptrDefaultName = ptrComponent->Direct("DefaultName");
if (ptrDefaultName) sInfo.ssDefaultObjectName = static_cast<std::string>(ptrDefaultName->GetValue());
else sInfo.ssDefaultObjectName = sInfo.ssClassName;
auto ptrType = ptrComponent->Direct("Type");
auto ptrDefaultName = ptrClass->Direct("DefaultName");
if (ptrDefaultName) sClass.ssDefaultObjectName = static_cast<std::string>(ptrDefaultName->GetValue());
else sClass.ssDefaultObjectName = sClass.ssName;
auto ptrType = ptrClass->Direct("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->Direct("Singleton");
sClass.eType = sdv::String2ObjectType(ptrType->GetValue());
if (sClass.eType == sdv::EObjectType::undefined)
continue;
auto ptrSingleton = ptrClass->Direct("Singleton");
if (ptrSingleton && static_cast<bool>(ptrSingleton->GetValue()))
sInfo.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
auto ptrDependencies = ptrComponent->Direct("Dependencies");
sClass.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
auto ptrDependencies = ptrClass->Direct("Dependencies");
if (ptrDependencies)
{
auto ptrDependencyArray = ptrDependencies->Cast<toml_parser::CArray>();
@@ -284,11 +292,11 @@ bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept
{
auto ptrDependsOn = ptrDependencyArray->Get(uiDependencyIndex);
if (ptrDependsOn)
sInfo.seqDependencies.push_back(static_cast<sdv::u8string>(ptrDependsOn->GetValue()));
sClass.seqDependencies.push_back(static_cast<sdv::u8string>(ptrDependsOn->GetValue()));
}
}
m_mapClassInfo[sInfo.ssClassName] = sInfo;
m_mapClassInfo[sClass.ssName] = sClass;
}
}
catch (const sdv::toml::XTOMLParseException&)

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef MODULE_H
#define MODULE_H

View File

@@ -1,9 +1,23 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "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"
#include "app_settings.h"
#include "app_config.h"
/// @cond DOXYGEN_IGNORE
#ifdef _WIN32
@@ -16,8 +30,14 @@
#endif
/// @endcond
CModuleControl::CModuleControl()
{}
// GetModuleControl might be redirected for unit tests.
#ifndef GetModuleControl
CModuleControl& GetModuleControl()
{
static CModuleControl module_control;
return module_control;
}
#endif // !defined GetModuleControl
CModuleControl::~CModuleControl()
{
@@ -123,7 +143,7 @@ sdv::core::TModuleID CModuleControl::Load(const sdv::u8string& ssModulePath)
pathModule = static_cast<std::string>(ssModulePath);
else
{
if (GetAppControl().IsMainApplication() || GetAppControl().IsIsolatedApplication())
if (GetAppSettings().IsMainApplication() || GetAppSettings().IsIsolatedApplication())
{
// Check the installation for the module.
pathModule = GetAppConfig().FindInstalledModule(static_cast<std::string>(ssModulePath));
@@ -180,7 +200,6 @@ sdv::core::TModuleID CModuleControl::Load(const sdv::u8string& ssModulePath)
// 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, "\"");
@@ -234,12 +253,13 @@ std::shared_ptr<CModuleInst> CModuleControl::FindModuleByClass(const std::string
// For main and isolated applications, check whether the module is in one of the installation manifests.
auto optManifest = GetAppConfig().FindInstalledComponent(rssClass);
if (!optManifest) return nullptr;
auto ssManifest = GetAppConfig().FindInstalledModuleManifest(optManifest->pathRelModule);
std::filesystem::path pathModule = std::filesystem::u8path(static_cast<std::string>(optManifest->ssModulePath));
auto ssManifest = GetAppConfig().FindInstalledModuleManifest(pathModule);
if (ssManifest.empty()) return nullptr;
lock.unlock();
// Load the module
return GetModule(ContextLoad(optManifest->pathRelModule, ssManifest));
return GetModule(ContextLoad(pathModule, ssManifest));
}
std::shared_ptr<CModuleInst> CModuleControl::GetModule(sdv::core::TModuleID tModuleID) const
@@ -281,28 +301,84 @@ void CModuleControl::ResetConfigBaseline()
m_setConfigModules.clear();
}
std::string CModuleControl::SaveConfig(const std::set<std::filesystem::path>& rsetIgnoreModule)
sdv::core::EConfigProcessResult CModuleControl::LoadModulesFromConfig(const CAppConfigFile& rconfig, bool bAllowPartialLoad)
{
// TODO EVE: Extract the parameters from the configuration and store them at the class info.
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::maintenance:
return sdv::core::EConfigProcessResult::successful; // Do not load anything
default:
break;
}
// First load the modules in the module list
auto vecModules = rconfig.GetModuleList();
size_t nSuccess = 0, nFail = 0;
for (const auto& rsModule : vecModules)
{
sdv::core::TModuleID tModuleID = Load(rsModule.pathModule.generic_u8string());
if (tModuleID)
++nSuccess;
else
++nFail;
}
// Load all the modules from the class list
auto vecClasses = rconfig.GetClassList();
for (const auto& rsClass : vecClasses)
{
if (rsClass.ssModulePath.empty()) continue;
sdv::core::TModuleID tModuleID = Load(rsClass.ssModulePath);
if (tModuleID)
++nSuccess;
else
++nFail;
}
// Load all the modules from the component list
auto vecComponents = rconfig.GetComponentList();
for (const auto& rsComponent : vecComponents)
{
if (rsComponent.pathModule.empty()) continue;
sdv::core::TModuleID tModuleID = Load(rsComponent.pathModule.generic_u8string());
if (tModuleID)
++nSuccess;
else
++nFail;
}
if (!bAllowPartialLoad && nFail) return sdv::core::EConfigProcessResult::failed;
if (!nFail) return sdv::core::EConfigProcessResult::successful;
if (nSuccess) return sdv::core::EConfigProcessResult::partially_successful;
return sdv::core::EConfigProcessResult::failed;
}
std::string CModuleControl::SaveConfig(const std::set<std::filesystem::path>& /*rsetIgnoreModule*/)
{
std::stringstream sstream;
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
//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;
}
}
//// 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
if (GetAppSettings().IsMaintenanceApplication()) return 0; // Not allowed
// Run through the manifest and check for complex services, applications and utilities.
// TODO EVE: Temporary suppression of cppcheck warning.
@@ -357,7 +433,6 @@ bool CModuleControl::ContextUnload(sdv::core::TModuleID tModuleID, bool bForce)
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);
@@ -395,17 +470,8 @@ void CModuleControl::AddCurrentPath()
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();
return GetAppSettings().IsStandaloneApplication() ||
GetAppSettings().IsEssentialApplication();
}
#endif

View File

@@ -1,14 +1,15 @@
/**
* @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 (c) 2025-2026 ZF Friedrichshafen AG
*
* @copyright Copyright ZF Friedrichshafen AG (c) 2021-2025
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
*/
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef MODULE_CONTROL_H
#define MODULE_CONTROL_H
@@ -21,6 +22,7 @@
#include <set>
#include "toml_parser/parser_toml.h"
#include "module.h"
#include "app_config_file.h"
/**
* @brief Module control class
@@ -34,7 +36,7 @@ public:
/**
* @brief Default constructor
*/
CModuleControl();
CModuleControl() = default;
/**
* @brief No copy constructor
@@ -153,6 +155,17 @@ public:
*/
void ResetConfigBaseline();
/**
* @brief Load all modules from the configuration and build a component class list.
* @attention This function doesn't do anything when the application is not running as a local application. In that case, the
* function returns no failure.
* @remarks Already loaded modules are not loaded again. This is not seen as a failure.
* @param[in] rconfig Reference to the configuration file.
* @param[in] bAllowPartialLoad When set, allow partial loading the configuration (one or more components).
* @return Returns the load result.
*/
sdv::core::EConfigProcessResult LoadModulesFromConfig(const CAppConfigFile& rconfig, bool bAllowPartialLoad);
/**
* @brief Save the configuration of all modules.
* @param[in] rsetIgnoreModule Set of modules not needing to add.
@@ -213,7 +226,11 @@ private:
TConfigSet m_setConfigModules; ///< Set with the modules for storing in the configuration.
};
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
/**
* @brief Return the module control.
* @return Reference to the module control.
*/
CModuleControl& GetModuleControl();
/**
* @brief Module control service
@@ -234,16 +251,10 @@ public:
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
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.
@@ -251,9 +262,7 @@ public:
static bool EnableModuleControlAccess();
};
DEFINE_SDV_OBJECT_NO_EXPORT(CModuleControlService)
#endif
DEFINE_SDV_OBJECT(CModuleControlService)
#ifdef _WIN32
#ifdef __GNUC__

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "object_lifetime_control.h"
#include "sdv_core.h"

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef OBJECT_LIFETIME_CONTROL_H
#define OBJECT_LIFETIME_CONTROL_H

View File

@@ -1,34 +1,36 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "repository.h"
#include "support/interface_ptr.h"
#include "sdv_core.h"
#include <iostream>
#include <cassert>
#include <algorithm>
#include "object_lifetime_control.h"
#include "../../global/base64.h"
#include "module_control.h"
#include "app_config.h"
#include "app_control.h"
#include "app_settings.h"
/**
* @brief Get the object type string
* @param[in] eType The object type to get the string from.
* @return The object type string.
*/
std::string GetObjectTypeString(sdv::EObjectType eType)
// GetRepository might be redirected for unit tests.
#ifndef GetRepository
CRepository& GetRepository()
{
std::string ssType;
switch (eType)
{
case sdv::EObjectType::SystemObject: ssType = "system object"; break;
case sdv::EObjectType::Device: ssType = "device"; break;
case sdv::EObjectType::BasicService: ssType = "basic_service"; break;
case sdv::EObjectType::ComplexService: ssType = "complex_service"; break;
case sdv::EObjectType::Application: ssType = "application"; break;
case sdv::EObjectType::Proxy: ssType = "proxy"; break;
case sdv::EObjectType::Stub: ssType = "stub"; break;
case sdv::EObjectType::Utility: ssType = "utility"; break;
default: ssType = "unknown"; break;
}
return ssType;
static CRepository repository;
return repository;
}
#endif // !defined GetRepository
void CRepository::SetConfigMode()
{
@@ -66,8 +68,8 @@ sdv::IInterfaceAccess* CRepository::GetObject(const sdv::u8string& ssObjectName)
// In case the object is not in the service map and this is a main or isolated application, create the object if the object is
// known in the installation manifest and is a system object.
auto optManifest = GetAppConfig().FindInstalledComponent(ssObjectName);
if (optManifest && optManifest->eType == sdv::EObjectType::SystemObject)
return GetObjectByID(CreateObject(optManifest->ssClassName, optManifest->ssDefaultObjectName, ""));
if (optManifest && optManifest->eType == sdv::EObjectType::system_object)
return GetObjectByID(CreateObject(optManifest->ssName, optManifest->ssDefaultObjectName, ""));
// Forward the request to core repository if one is linked here (this can only occur with isolated and external applications).
if (!m_ptrCoreRepoAccess) return nullptr;
@@ -114,10 +116,10 @@ sdv::IInterfaceAccess* CRepository::CreateUtility(/*in*/ const sdv::u8string& ss
SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!");
return nullptr;
}
if (optClassInfo->eType != sdv::EObjectType::Utility)
if (optClassInfo->eType != sdv::EObjectType::utility)
{
// Utilities and marshall objects cannot be created using the CreateObject function.
SDV_LOG_ERROR("Creation of an utility of invalid type \"", GetObjectTypeString(optClassInfo->eType),
SDV_LOG_ERROR("Creation of an utility of invalid type \"", sdv::ObjectType2String(optClassInfo->eType),
"\" requested for class \"", ssClassName, "\"!");
return nullptr;
}
@@ -147,7 +149,7 @@ sdv::IInterfaceAccess* CRepository::CreateUtility(/*in*/ const sdv::u8string& ss
lock.unlock();
// Print info
if (GetAppControl().IsConsoleVerbose())
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Creating a utility #" << ptrObjectEntry->tObjectID << " of type " << ptrObjectEntry->ssName << std::endl;
// Create the object
@@ -160,7 +162,7 @@ sdv::IInterfaceAccess* CRepository::CreateUtility(/*in*/ const sdv::u8string& ss
if (pObjectControl)
{
pObjectControl->Initialize(ssObjectConfig);
if (pObjectControl->GetStatus() != sdv::EObjectStatus::running)
if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized)
{
// Destroy the object
ptrModule->DestroyObject(ptrObject);
@@ -222,10 +224,10 @@ sdv::IInterfaceAccess* CRepository::CreateProxyObject(/*in*/ sdv::interface_id i
SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!");
return nullptr;
}
if (optClassInfo->eType != sdv::EObjectType::Proxy)
if (optClassInfo->eType != sdv::EObjectType::proxy)
{
// Utilities and marshall objects cannot be created using the CreateObject function.
SDV_LOG_ERROR("Creation of an object of invalid type \"", GetObjectTypeString(optClassInfo->eType),
SDV_LOG_ERROR("Creation of an object of invalid type \"", sdv::ObjectType2String(optClassInfo->eType),
"\" requested for class \"", ssClassName, "\"!");
return nullptr;
}
@@ -255,7 +257,7 @@ sdv::IInterfaceAccess* CRepository::CreateProxyObject(/*in*/ sdv::interface_id i
lock.unlock();
// Print info
if (GetAppControl().IsConsoleVerbose())
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Creating a proxy object #" << ptrObjectEntry->tObjectID << " of type " <<
ptrObjectEntry->ssName << std::endl;
@@ -269,7 +271,7 @@ sdv::IInterfaceAccess* CRepository::CreateProxyObject(/*in*/ sdv::interface_id i
if (pObjectControl)
{
pObjectControl->Initialize("");
if (pObjectControl->GetStatus() != sdv::EObjectStatus::running)
if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized)
{
// Destroy the object
ptrModule->DestroyObject(ptrObject);
@@ -331,10 +333,10 @@ sdv::IInterfaceAccess* CRepository::CreateStubObject(/*in*/ sdv::interface_id id
SDV_LOG_ERROR("Object creation requested but object class was not found \"", ssClassName, "\"!");
return nullptr;
}
if (optClassInfo->eType != sdv::EObjectType::Stub)
if (optClassInfo->eType != sdv::EObjectType::stub)
{
// Utilities and marshall objects cannot be created using the CreateObject function.
SDV_LOG_ERROR("Creation of an object of invalid type \"", GetObjectTypeString(optClassInfo->eType),
SDV_LOG_ERROR("Creation of an object of invalid type \"", sdv::ObjectType2String(optClassInfo->eType),
"\" requested for class \"", ssClassName, "\"!");
return nullptr;
}
@@ -364,7 +366,7 @@ sdv::IInterfaceAccess* CRepository::CreateStubObject(/*in*/ sdv::interface_id id
lock.unlock();
// Print info
if (GetAppControl().IsConsoleVerbose())
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Creating a stub #" << ptrObjectEntry->tObjectID << " of type " << ptrObjectEntry->ssName << std::endl;
// Create the object
@@ -377,7 +379,7 @@ sdv::IInterfaceAccess* CRepository::CreateStubObject(/*in*/ sdv::interface_id id
if (pObjectControl)
{
pObjectControl->Initialize("");
if (pObjectControl->GetStatus() != sdv::EObjectStatus::running)
if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized)
{
// Destroy the object
ptrModule->DestroyObject(ptrObject);
@@ -436,6 +438,9 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName
return false;
}
// TODO: Request the parameters from the class info and create a config with them. Then add the parameters from the supplied
// config and overwrite existing and extend non-existing parameters. Then create a new config string.
// Check the class type
auto optClassInfo = ptrModule->GetClassInfo(ssClassName);
if (!optClassInfo)
@@ -445,22 +450,27 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName
}
bool bError = false; // Creation not allowed
bool bIsolate = false; // Creation should be isolated
bool bDeviceAndBasicServiceAllowed = GetAppControl().IsMainApplication() || GetAppControl().IsStandaloneApplication() ||
GetAppControl().IsEssentialApplication();
bool bComplexServiceAllowed = !GetAppControl().IsMaintenanceApplication() &&
((!GetAppControl().IsIsolatedApplication() && !GetAppControl().IsExternalApplication()) || !m_bIsoObjectLoaded);
bool bDeviceAndBasicServiceAllowed = GetAppSettings().IsMainApplication() || GetAppSettings().IsStandaloneApplication() ||
GetAppSettings().IsEssentialApplication();
bool bComplexServiceAllowed = !GetAppSettings().IsMaintenanceApplication() &&
((!GetAppSettings().IsIsolatedApplication() && !GetAppSettings().IsExternalApplication()) || !m_bIsoObjectLoaded);
switch (optClassInfo->eType)
{
case sdv::EObjectType::SystemObject:
case sdv::EObjectType::system_object:
break;
case sdv::EObjectType::Device:
case sdv::EObjectType::device:
case sdv::EObjectType::platform_abstraction:
case sdv::EObjectType::vehicle_bus:
bError = !bDeviceAndBasicServiceAllowed;
break;
case sdv::EObjectType::BasicService:
case sdv::EObjectType::basic_service:
case sdv::EObjectType::sensor:
case sdv::EObjectType::actuator:
bError = !bDeviceAndBasicServiceAllowed;
break;
case sdv::EObjectType::ComplexService:
bIsolate = GetAppControl().IsMainApplication();
case sdv::EObjectType::complex_service:
case sdv::EObjectType::vehicle_function:
bIsolate = GetAppSettings().IsMainApplication();
bError = !bComplexServiceAllowed;
m_bIsoObjectLoaded = true;
break;
@@ -471,17 +481,69 @@ sdv::core::TObjectID CRepository::CreateObject2(const sdv::u8string& ssClassName
if (bError)
{
// Utilities and marshall objects cannot be created using the CreateObject function.
SDV_LOG_ERROR("Creation of an object of invalid/unallowed type \"", GetObjectTypeString(optClassInfo->eType),
SDV_LOG_ERROR("Creation of an object of invalid/unallowed type \"", sdv::ObjectType2String(optClassInfo->eType),
"\" requested for class \"", ssClassName, "\"!");
return 0;
}
// Check for an object name. If not existing get the default name (being either one specified by the object or the class name).
std::shared_lock<std::shared_mutex> lock(m_mtxObjects);
std::string ssObjectName2 = ssObjectName;
if (ssObjectName2.empty())
ssObjectName2 = optClassInfo->ssDefaultObjectName;
if (ssObjectName2.empty())
ssObjectName2 = ssClassName;
// Check for an object with the same name.
auto itPreviousService = m_mapServiceObjects.find(ssObjectName2);
if (itPreviousService != m_mapServiceObjects.end())
{
// Object entry is valid?
if (!*itPreviousService->second)
{
// This should not occur... there is a previous object in the service map, but the object is empty.
SDV_LOG_ERROR("Object creation requested for class \"", ssClassName, "\", but object with the same name \"",
ssObjectName2, "\" was already instantiated, but cannot be found!");
return 0;
}
// Trying to create an object with the same name is not an error if the classes are identical.
if ((*itPreviousService->second)->sClassInfo.ssName == ssClassName)
return (*itPreviousService->second)->tObjectID;
// Object name was already used by another class. This is an error.
SDV_LOG_ERROR("Object creation requested for class \"", ssClassName, "\", but object with the same name \"",
ssObjectName2, "\" was already instantiated for class \"", (*itPreviousService->second)->sClassInfo.ssName,
"\"!");
return 0;
}
// Check with singleton objects if the object was already instantiated.
if (optClassInfo->uiFlags & static_cast<uint32_t>(sdv::EObjectFlags::singleton))
{
for (const auto& rprService : m_mapServiceObjects)
{
if (!*rprService.second)
continue;
if ((*rprService.second)->ptrModule && (*rprService.second)->ptrModule->GetModuleID() == ptrModule->GetModuleID()
&& (*rprService.second)->sClassInfo.ssName == ssClassName)
{
SDV_LOG_ERROR("Object creation requested but object from the same class \"", ssClassName,
"\" was already instantiated and only one instance is allowed!");
return 0;
}
}
}
// Unlock; the next function will lock again.
lock.unlock();
// Create an isolated object.
if (bIsolate)
return CreateIsolatedObject(*optClassInfo, ssObjectName, ssObjectConfig);
return CreateIsolatedObject(*optClassInfo, ssObjectName2, ssObjectConfig);
// Create an internal object.
return InternalCreateObject(ptrModule, *optClassInfo, ssObjectName, ssObjectConfig);
return InternalCreateObject(ptrModule, *optClassInfo, ssObjectName2, ssObjectConfig);
}
sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModuleID tModuleID,
@@ -505,25 +567,30 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu
return false;
}
bool bError = false;
bool bDeviceAndBasicServiceAllowed = GetAppControl().IsMainApplication() || GetAppControl().IsStandaloneApplication() ||
GetAppControl().IsEssentialApplication();
bool bComplexServiceAllowed = !GetAppControl().IsMaintenanceApplication() &&
((!GetAppControl().IsIsolatedApplication() && !GetAppControl().IsExternalApplication()) || !m_bIsoObjectLoaded);
bool bDeviceAndBasicServiceAllowed = GetAppSettings().IsMainApplication() || GetAppSettings().IsStandaloneApplication() ||
GetAppSettings().IsEssentialApplication();
bool bComplexServiceAllowed = !GetAppSettings().IsMaintenanceApplication() &&
((!GetAppSettings().IsIsolatedApplication() && !GetAppSettings().IsExternalApplication()) || !m_bIsoObjectLoaded);
switch (optClassInfo->eType)
{
case sdv::EObjectType::SystemObject:
case sdv::EObjectType::system_object:
break;
case sdv::EObjectType::Device:
case sdv::EObjectType::device:
case sdv::EObjectType::platform_abstraction:
case sdv::EObjectType::vehicle_bus:
// Allowed?
bError = !bDeviceAndBasicServiceAllowed;
break;
case sdv::EObjectType::BasicService:
case sdv::EObjectType::basic_service:
case sdv::EObjectType::sensor:
case sdv::EObjectType::actuator:
// Allowed?
bError = !bDeviceAndBasicServiceAllowed;
break;
case sdv::EObjectType::ComplexService:
case sdv::EObjectType::complex_service:
case sdv::EObjectType::vehicle_function:
// Isolation (which is needed for the main application) is not supported for direct creation.
bError = !bComplexServiceAllowed || GetAppControl().IsMainApplication();
bError = !bComplexServiceAllowed || GetAppSettings().IsMainApplication();
m_bIsoObjectLoaded = true;
break;
default:
@@ -533,13 +600,73 @@ sdv::core::TObjectID CRepository::CreateObjectFromModule(/*in*/ sdv::core::TModu
if (bError)
{
// Utilities and marshall objects cannot be created using the CreateObject function.
SDV_LOG_ERROR("Creation of an object of invalid type \"", GetObjectTypeString(optClassInfo->eType),
SDV_LOG_ERROR("Creation of an object of invalid type \"", sdv::ObjectType2String(optClassInfo->eType),
"\" requested for class \"", ssClassName, "\"!");
return 0;
}
// Check for an object name. If not existing get the default name (being either one specified by the object or the class name).
std::shared_lock<std::shared_mutex> lock(m_mtxObjects);
std::string ssObjectName2 = ssObjectName;
if (ssObjectName2.empty())
ssObjectName2 = optClassInfo->ssDefaultObjectName;
if (ssObjectName2.empty())
ssObjectName2 = ssClassName;
// Check for an object with the same name.
auto itPreviousService = m_mapServiceObjects.find(ssObjectName2);
if (itPreviousService != m_mapServiceObjects.end())
{
// Object entry is valid?
if (!*itPreviousService->second)
{
// This should not occur... there is a previous object in the service map, but the object is empty.
SDV_LOG_ERROR("Object creation requested for class \"",
ssClassName,
"\", but object with the same name \"",
ssObjectName2,
"\" was already instantiated, but cannot be found!");
return 0;
}
// Trying to create an object with the same name is not an error if the classes are identical.
if ((*itPreviousService->second)->sClassInfo.ssName == ssClassName)
return (*itPreviousService->second)->tObjectID;
// Object name was already used by another class. This is an error.
SDV_LOG_ERROR("Object creation requested for class \"",
ssClassName,
"\", but object with the same name \"",
ssObjectName2,
"\" was already instantiated for class \"",
(*itPreviousService->second)->sClassInfo.ssName,
"\"!");
return 0;
}
// Check with singleton objects if the object was already instantiated.
if (optClassInfo->uiFlags & static_cast<uint32_t>(sdv::EObjectFlags::singleton))
{
for (const auto& rprService : m_mapServiceObjects)
{
if (!*rprService.second)
continue;
if ((*rprService.second)->ptrModule && (*rprService.second)->ptrModule->GetModuleID() == ptrModule->GetModuleID()
&& (*rprService.second)->sClassInfo.ssName == ssClassName)
{
SDV_LOG_ERROR("Object creation requested but object from the same class \"",
ssClassName,
"\" was already instantiated and only one instance is allowed!");
return 0;
}
}
}
// Unlock; the next function will lock again.
lock.unlock();
// Create an internal object.
return InternalCreateObject(ptrModule, *optClassInfo, ssObjectName, ssObjectConfig);
return InternalCreateObject(ptrModule, *optClassInfo, ssObjectName2, ssObjectConfig);
}
bool CRepository::DestroyObject(/*in*/ const sdv::u8string& ssObjectName)
@@ -555,9 +682,9 @@ bool CRepository::DestroyObject2(/*in*/ const sdv::u8string& ssObjectName)
// Print info
auto ptrObjectEntry = *itService->second;
if (GetAppControl().IsConsoleVerbose())
std::cout << "Destroy a " << GetObjectTypeString(ptrObjectEntry->sClassInfo.eType) << " #" << ptrObjectEntry->tObjectID <<
" of type " << ptrObjectEntry->sClassInfo.ssClassName << " with the name " << ssObjectName << std::endl;
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Destroy a " << sdv::ObjectType2String(ptrObjectEntry->sClassInfo.eType) << " #" << ptrObjectEntry->tObjectID <<
" of type " << ptrObjectEntry->sClassInfo.ssName << " with the name " << ssObjectName << std::endl;
// Remove the service
m_lstOrderedServiceObjects.erase(itService->second);
@@ -569,7 +696,6 @@ bool CRepository::DestroyObject2(/*in*/ const sdv::u8string& ssObjectName)
// Remove the object from the list and map.
if (!ptrObjectEntry) return false; // Failure: should not happen!
m_mapObjects.erase(ptrObjectEntry->tObjectID);
m_setConfigObjects.erase(ptrObjectEntry->tObjectID); // Erase from config if part of config.
lock.unlock();
// Try to destroy objects that depend on this object first
@@ -594,13 +720,13 @@ bool CRepository::DestroyObject2(/*in*/ const sdv::u8string& ssObjectName)
if (ptrDependingObject->bControlled) DestroyObject(ptrDependingObject->ssName);
}
};
fnDestroyDependingObjects(GetDependingObjectInstancesByClass(ptrObjectEntry->sClassInfo.ssClassName));
if (ptrObjectEntry->sClassInfo.ssClassName != ptrObjectEntry->sClassInfo.ssDefaultObjectName)
fnDestroyDependingObjects(GetDependingObjectInstancesByClass(ptrObjectEntry->sClassInfo.ssName));
if (ptrObjectEntry->sClassInfo.ssName != ptrObjectEntry->sClassInfo.ssDefaultObjectName)
fnDestroyDependingObjects(GetDependingObjectInstancesByClass(ptrObjectEntry->sClassInfo.ssDefaultObjectName));
// Shutdown the object (not all objects expose IObjectControl).
auto* pObjectControl = ptrObjectEntry->ptrObject.GetInterface<sdv::IObjectControl>();
if (pObjectControl && pObjectControl->GetStatus() != sdv::EObjectStatus::destruction_pending)
if (pObjectControl && pObjectControl->GetObjectState() != sdv::EObjectState::destruction_pending)
pObjectControl->Shutdown();
// Destroy the object (not all objects have a module and some are wrapped by an isolation monitor).
@@ -646,7 +772,7 @@ sdv::core::TObjectID CRepository::RegisterObject(IInterfaceAccess* pObjectIfc, c
lock.unlock();
if (!ptrIsolatedObject) return 0; // Already deleted
if (ptrIsolatedObject->ptrObject) return 0; // only one time registration allowed.
if (GetAppControl().IsConsoleVerbose())
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Registering the isolated object #" << ptrIsolatedObject->tObjectID << " with the name " << ssObjectName << std::endl;
ptrIsolatedObject->ptrObject = pObjectIfc;
std::unique_lock<std::mutex> lockObject(ptrIsolatedObject->mtxConnect);
@@ -673,7 +799,7 @@ sdv::core::TObjectID CRepository::RegisterObject(IInterfaceAccess* pObjectIfc, c
ptrObjectEntry->bControlled = true;
// Print info
if (GetAppControl().IsConsoleVerbose())
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Registering a application object #" << tObjectID << " with the name " << ssObjectName << std::endl;
return tObjectID;
@@ -695,19 +821,11 @@ sdv::SClassInfo CRepository::FindClass(/*in*/ const sdv::u8string& ssClassName)
std::shared_lock<std::shared_mutex> lock(m_mtxObjects);
// For main and isolated applications, search in the installation.
if (GetAppControl().IsMainApplication() || GetAppControl().IsIsolatedApplication())
if (GetAppSettings().IsMainApplication() || GetAppSettings().IsIsolatedApplication())
{
auto optManifest = GetAppConfig().FindInstalledComponent(ssClassName);
if (!optManifest) return {};
sdv::SClassInfo sClassInfo{};
sClassInfo.ssModulePath = optManifest->pathRelModule.generic_u8string();
sClassInfo.ssClassName = optManifest->ssClassName;
sClassInfo.seqClassAliases = optManifest->seqAliases;
sClassInfo.ssDefaultObjectName = optManifest->ssDefaultObjectName;
sClassInfo.eType = optManifest->eType;
sClassInfo.uiFlags = optManifest->uiFlags;
sClassInfo.seqDependencies = optManifest->seqDependencies;
return sClassInfo;
return *optManifest;
}
// Get the information through the module.
@@ -833,7 +951,7 @@ void CRepository::OnDestroyObject(sdv::IInterfaceAccess* pObject)
// Shutdown the object
auto* pObjectControl = ptrObjectEntry->ptrObject.GetInterface<sdv::IObjectControl>();
if (pObjectControl && pObjectControl->GetStatus() != sdv::EObjectStatus::destruction_pending)
if (pObjectControl && pObjectControl->GetObjectState() != sdv::EObjectState::destruction_pending)
pObjectControl->Shutdown();
// Destroy the object
@@ -864,7 +982,7 @@ void CRepository::DestroyModuleObjects(sdv::core::TModuleID tModuleID)
// Shutdown the object
auto* pObjectControl = ptrObjectEntry->ptrObject.GetInterface<sdv::IObjectControl>();
if (pObjectControl && pObjectControl->GetStatus() != sdv::EObjectStatus::destruction_pending)
if (pObjectControl && pObjectControl->GetObjectState() != sdv::EObjectState::destruction_pending)
pObjectControl->Shutdown();
// Destroy the object
@@ -924,34 +1042,114 @@ void CRepository::DestroyAllObjects(const std::vector<std::string>& rvecIgnoreOb
void CRepository::ResetConfigBaseline()
{
// Reset the configuration set.
std::unique_lock<std::shared_mutex> lock(m_mtxObjects);
m_setConfigObjects.clear();
for (const auto& rvtObject : m_mapObjects)
m_setConfigObjects.insert(rvtObject.first);
lock.unlock();
GetModuleControl().ResetConfigBaseline();
}
sdv::core::EConfigProcessResult CRepository::StartFromConfig(const CAppConfigFile& rconfig, bool bAllowPartialLoad)
{
// First load the modules
sdv::core::EConfigProcessResult eResult = GetModuleControl().LoadModulesFromConfig(rconfig, bAllowPartialLoad);
if (eResult == sdv::core::EConfigProcessResult::failed) return eResult;
bool bRunsAsServer = false;
switch (GetAppSettings().GetContextType())
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
case sdv::app::EAppContext::maintenance:
bRunsAsServer = true;
default:
break;
}
// Load all the components from the component list
auto vecComponents = rconfig.GetComponentList();
size_t nSuccess = 0, nFail = 0;
std::vector<std::string> vecLoadedObjects;
for (const auto& rsComponent : vecComponents)
{
// If there a path is stored with the component and not running a server, load and get the module ID and then start a
// specific component. If there is no path stored, use the default object creation function.
sdv::core::TObjectID tObjectID = 0;
if (!bRunsAsServer && !rsComponent.pathModule.empty())
{
sdv::core::TModuleID tModuleID = GetModuleControl().Load(rsComponent.pathModule.generic_u8string());
if (tModuleID)
tObjectID = CreateObjectFromModule(tModuleID, rsComponent.ssClassName, rsComponent.ssInstanceName, rsComponent.ssParameterTOML);
} else
tObjectID = CreateObject(rsComponent.ssClassName, rsComponent.ssInstanceName, rsComponent.ssParameterTOML);
if (tObjectID)
{
++nSuccess;
// Request the object name. This name is unique, but might be assigned automatically.
vecLoadedObjects.push_back(GetObjectInfo(tObjectID).ssObjectName);
} else
++nFail;
if (!bAllowPartialLoad && nFail)
break;
}
// Destroy the objects when an error occurred during loading.
if (!bAllowPartialLoad && nFail)
{
for (const std::string& rssName : vecLoadedObjects)
DestroyObject(rssName);
return sdv::core::EConfigProcessResult::failed;
}
// TODO: The class list contains the parameters to set for each component.
//// Load the class information from the class list
// auto vecClasses = rconfig.GetClassList();
// for (const auto& rsClass : vecClasses)
//{
// if (rsClass.ssModulePath.empty())
// continue;
// sdv::core::TModuleID tModuleID = Load(std::filesystem::u8path(rsClass.ssModulePath));
// if (tModuleID)
// ++nSuccess;
// else
// ++nFail;
//}
//if (!bAllowPartialLoad && nFail)
// return sdv::core::EConfigProcessResult::failed;
if (!nFail)
return sdv::core::EConfigProcessResult::successful;
if (nSuccess)
return sdv::core::EConfigProcessResult::partially_successful;
return sdv::core::EConfigProcessResult::failed;
}
std::string CRepository::SaveConfig()
{
std::stringstream sstream;
std::shared_lock<std::shared_mutex> lock(m_mtxObjects);
std::set<std::filesystem::path> setModules;
for (const std::shared_ptr<SObjectEntry>& rptrObject : m_lstOrderedServiceObjects)
{
if (m_setConfigObjects.find(rptrObject->tObjectID) != m_setConfigObjects.end())
{
sstream << std::endl;
sstream << "[[Component]]" << std::endl;
sstream << "Path = \"" << rptrObject->ptrModule->GetModulePath().generic_u8string() << "\"" << std::endl;
setModules.insert(rptrObject->ptrModule->GetModulePath());
sstream << "Class = \"" << rptrObject->sClassInfo.ssClassName << "\"" << std::endl;
sstream << "Name = \"" << rptrObject->ssName << "\"" << std::endl;
// TODO: attributes...
}
}
//std::shared_lock<std::shared_mutex> lock(m_mtxObjects);
//std::set<std::filesystem::path> setModules;
//for (const std::shared_ptr<SObjectEntry>& rptrObject : m_lstOrderedServiceObjects)
//{
// if (m_setConfigObjects.find(rptrObject->tObjectID) != m_setConfigObjects.end())
// {
// sstream << std::endl;
// sstream << "[[Component]]" << std::endl;
// sstream << "Path = \"" << rptrObject->ptrModule->GetModulePath().generic_u8string() << "\"" << std::endl;
// setModules.insert(rptrObject->ptrModule->GetModulePath());
// sstream << "Class = \"" << rptrObject->sClassInfo.ssName << "\"" << std::endl;
// sstream << "Name = \"" << rptrObject->ssName << "\"" << std::endl;
// // TODO: attributes...
// }
//}
// Add the modules
sstream << GetModuleControl().SaveConfig(setModules);
//// Add the modules
//sstream << GetModuleControl().SaveConfig(setModules);
return sstream.str();
}
@@ -960,10 +1158,27 @@ sdv::core::TObjectID CRepository::CreateIsolatedObject(const sdv::SClassInfo& rs
const sdv::u8string& rssObjectConfig)
{
// Check whether running as main application.
if (!GetAppControl().IsMainApplication()) return 0;
if (!GetAppSettings().IsMainApplication()) return 0;
// Check the object type
if (rsClassInfo.eType != sdv::EObjectType::ComplexService && rsClassInfo.eType != sdv::EObjectType::Utility) return 0;
if (rssObjectName.empty())
{
SDV_LOG_ERROR("Object creation requested for class \"", rsClassInfo.ssName, "\", but no object name was provided!");
return 0;
}
// Check the object type and define whether this should be a controlled object
bool bControlled = false;
switch (rsClassInfo.eType)
{
case sdv::EObjectType::complex_service:
case sdv::EObjectType::vehicle_function:
bControlled = true;
break;
case sdv::EObjectType::utility:
break;
default:
return 0;
}
// Create server connection (to the repository service object... not to this class!).
sdv::com::IConnectionControl* pConnectionControl =
@@ -979,7 +1194,7 @@ sdv::core::TObjectID CRepository::CreateIsolatedObject(const sdv::SClassInfo& rs
// Create the isolation process configuration
std::stringstream sstreamConfig;
sstreamConfig << "[Isolation]" << std::endl;
sstreamConfig << "Class = \"" << rsClassInfo.ssClassName << "\"" << std::endl;
sstreamConfig << "Class = \"" << rsClassInfo.ssName << "\"" << std::endl;
sstreamConfig << "Object = \"" << rssObjectName << "\"" << std::endl << std::endl;
if (!rssObjectConfig.empty())
{
@@ -992,12 +1207,12 @@ sdv::core::TObjectID CRepository::CreateIsolatedObject(const sdv::SClassInfo& rs
// Create command line arguments
sdv::sequence<sdv::u8string> seqArguments;
seqArguments.push_back(Base64EncodePlainText(sstreamConfig.str()));
if (GetAppControl().IsConsoleVerbose())
if (GetAppSettings().IsConsoleVerbose())
seqArguments.push_back("--verbose");
else
seqArguments.push_back("--silent");
seqArguments.push_back("--instance" + std::to_string(GetAppControl().GetInstanceID()));
seqArguments.push_back("--install_dir" + GetAppControl().GetInstallDir().generic_u8string());
seqArguments.push_back("--instance" + std::to_string(GetAppSettings().GetInstanceID()));
seqArguments.push_back("--install_dir" + GetAppSettings().GetRootDir().generic_u8string());
sdv::core::TObjectID tObjectID = CreateObjectID();
auto ptrObjectEntry = std::make_shared<SObjectEntry>();
@@ -1006,11 +1221,10 @@ sdv::core::TObjectID CRepository::CreateIsolatedObject(const sdv::SClassInfo& rs
// cppcheck-suppress knownConditionTrueFalse
if (!ptrObjectEntry)
{
SDV_LOG_ERROR("Object creation failed for class \"", rsClassInfo.ssClassName, "\"!");
SDV_LOG_ERROR("Object creation failed for class \"", rsClassInfo.ssName, "\"!");
return 0;
}
std::unique_lock<std::shared_mutex> lock(m_mtxObjects);
bool bControlled = rsClassInfo.eType == sdv::EObjectType::ComplexService;
ptrObjectEntry->tObjectID = tObjectID;
ptrObjectEntry->sClassInfo = rsClassInfo;
ptrObjectEntry->ssName = rssObjectName;
@@ -1019,15 +1233,14 @@ sdv::core::TObjectID CRepository::CreateIsolatedObject(const sdv::SClassInfo& rs
ptrObjectEntry->bIsolated = true;
if (bControlled)
m_mapServiceObjects[rssObjectName] = m_lstOrderedServiceObjects.insert(m_lstOrderedServiceObjects.end(), ptrObjectEntry);
m_setConfigObjects.insert(tObjectID);
m_mapObjects[tObjectID] = ptrObjectEntry;
m_mapIsolatedObjects[rssObjectName] = ptrObjectEntry;
lock.unlock();
// Print info
if (GetAppControl().IsConsoleVerbose())
std::cout << "Creating an isolated " << GetObjectTypeString(rsClassInfo.eType) << " #" << tObjectID << " of type " <<
rsClassInfo.ssClassName << std::endl;
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Creating an isolated " << sdv::ObjectType2String(rsClassInfo.eType) << " #" << tObjectID << " of type " <<
rsClassInfo.ssName << std::endl;
// Start the isolation process
sdv::process::IProcessControl* pProcessControl = sdv::core::GetObject<sdv::process::IProcessControl>("ProcessControlService");
@@ -1046,51 +1259,14 @@ sdv::core::TObjectID CRepository::CreateIsolatedObject(const sdv::SClassInfo& rs
sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr<CModuleInst>& rptrModule,
const sdv::SClassInfo& rsClassInfo, const sdv::u8string& rssObjectName, const sdv::u8string& rssObjectConfig)
{
// Check for an object name. If not existing get the default name (being either one specified by the object or the class name).
std::unique_lock<std::shared_mutex> lock(m_mtxObjects);
std::string ssObjectName2 = rssObjectName;
if (rssObjectName.empty())
ssObjectName2 = rptrModule->GetDefaultObjectName(rsClassInfo.ssClassName);
// Check for an object with the same name.
auto itPreviousService = m_mapServiceObjects.find(ssObjectName2);
if (itPreviousService != m_mapServiceObjects.end())
{
// Object entry is valid?
if (!*itPreviousService->second)
{
// This should not occur... there is a previous object in the service map, but the object is empty.
SDV_LOG_ERROR("Object creation requested for class \"", rsClassInfo.ssClassName, "\", but object with the same name \"",
ssObjectName2, "\" was already instantiated, but cannot be found!");
return 0;
}
// Trying to create an object with the same name is not an error if the classes are identical.
if ((*itPreviousService->second)->sClassInfo.ssClassName == rsClassInfo.ssClassName)
return (*itPreviousService->second)->tObjectID;
// Object name was already used by another class. This is an error.
SDV_LOG_ERROR("Object creation requested for class \"", rsClassInfo.ssClassName, "\", but object with the same name \"",
ssObjectName2, "\" was already instantiated for class \"", (*itPreviousService->second)->sClassInfo.ssClassName, "\"!");
SDV_LOG_ERROR("Object creation requested for class \"", rsClassInfo.ssName, "\", but no object name was provided!");
return 0;
}
// Check with singleton objects if the object was already instantiated.
if (rsClassInfo.uiFlags & static_cast<uint32_t>(sdv::EObjectFlags::singleton))
{
for (const auto& rprService : m_mapServiceObjects)
{
if (!*rprService.second) continue;
if ((*rprService.second)->ptrModule && (*rprService.second)->ptrModule->GetModuleID() == rptrModule->GetModuleID() &&
(*rprService.second)->sClassInfo.ssClassName == rsClassInfo.ssClassName)
{
SDV_LOG_ERROR("Object creation requested but object from the same class \"",
rsClassInfo.ssClassName, "\" was already instantiated and only one instance is allowed!");
return 0;
}
}
}
// Check for an object name. If not existing get the default name (being either one specified by the object or the class name).
std::unique_lock<std::shared_mutex> lock(m_mtxObjects);
// Reserve the object and return the lock
sdv::core::TObjectID tObjectID = CreateObjectID();
auto ptrObjectEntry = std::make_shared<SObjectEntry>();
@@ -1099,31 +1275,30 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr<CMo
// cppcheck-suppress knownConditionTrueFalse
if (!ptrObjectEntry)
{
SDV_LOG_ERROR("Object creation failed for class \"", rsClassInfo.ssClassName, "\"!");
SDV_LOG_ERROR("Object creation failed for class \"", rsClassInfo.ssName, "\"!");
return 0;
}
m_mapServiceObjects[ssObjectName2] = m_lstOrderedServiceObjects.insert(m_lstOrderedServiceObjects.end(), ptrObjectEntry);
m_setConfigObjects.insert(tObjectID);
m_mapServiceObjects[rssObjectName] = m_lstOrderedServiceObjects.insert(m_lstOrderedServiceObjects.end(), ptrObjectEntry);
m_mapObjects[tObjectID] = ptrObjectEntry;
ptrObjectEntry->tObjectID = tObjectID;
ptrObjectEntry->sClassInfo = rsClassInfo;
ptrObjectEntry->ssName = ssObjectName2;
ptrObjectEntry->ssName = rssObjectName;
ptrObjectEntry->ssConfig = rssObjectConfig;
ptrObjectEntry->ptrModule = rptrModule;
ptrObjectEntry->bControlled = true;
lock.unlock();
// Print info
if (GetAppControl().IsConsoleVerbose())
std::cout << "Creating a " << GetObjectTypeString(rsClassInfo.eType) << " #" << tObjectID << " of type " <<
rsClassInfo.ssClassName << " with the name " << ssObjectName2 << std::endl;
if (GetAppSettings().IsConsoleVerbose())
std::cout << "Creating a " << sdv::ObjectType2String(rsClassInfo.eType) << " #" << tObjectID << " of type " <<
rsClassInfo.ssName << " with the name " << rssObjectName << std::endl;
// Create the object
sdv::TInterfaceAccessPtr ptrObject = rptrModule->CreateObject(rsClassInfo.ssClassName);
sdv::TInterfaceAccessPtr ptrObject = rptrModule->CreateObject(rsClassInfo.ssName);
if (!ptrObject)
{
// Destroy the object again
DestroyObject(ssObjectName2);
DestroyObject(rssObjectName);
return 0;
}
@@ -1132,7 +1307,7 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr<CMo
if (pObjectControl)
{
pObjectControl->Initialize(rssObjectConfig);
if (pObjectControl->GetStatus() != sdv::EObjectStatus::initialized)
if (pObjectControl->GetObjectState() != sdv::EObjectState::initialized)
{
// Shutdown the object (even if the initialization didn't work properly).
pObjectControl->Shutdown();
@@ -1141,7 +1316,7 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr<CMo
rptrModule->DestroyObject(ptrObject);
// Destroy the object again
DestroyObject(ssObjectName2);
DestroyObject(rssObjectName);
return 0;
}
switch (GetAppControl().GetOperationState())
@@ -1158,7 +1333,8 @@ sdv::core::TObjectID CRepository::InternalCreateObject(const std::shared_ptr<CMo
}
// For an one isolated object: add iso control and store this as pointer in object entry...
if (rsClassInfo.eType == sdv::EObjectType::ComplexService && GetAppControl().IsIsolatedApplication())
if ((rsClassInfo.eType == sdv::EObjectType::complex_service || rsClassInfo.eType == sdv::EObjectType::vehicle_function) &&
GetAppSettings().IsIsolatedApplication())
{
ptrObjectEntry->ptrIsoMon = std::make_shared<CIsoMonitor>(ptrObject);
ptrObjectEntry->ptrObject = ptrObjectEntry->ptrIsoMon.get();
@@ -1199,32 +1375,23 @@ std::vector<sdv::core::TObjectID> CRepository::GetDependingObjectInstancesByClas
return vecDependingObjects;
}
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
CRepository& CRepositoryService::GetRepository()
{
return ::GetRepository();
}
bool CRepositoryService::EnableRepositoryObjectControl()
{
return GetAppControl().IsMainApplication() ||
GetAppControl().IsStandaloneApplication() ||
GetAppControl().IsEssentialApplication() ||
GetAppControl().IsIsolatedApplication();
return GetAppSettings().IsMainApplication() ||
GetAppSettings().IsStandaloneApplication() ||
GetAppSettings().IsEssentialApplication() ||
GetAppSettings().IsIsolatedApplication();
}
bool CRepositoryService::EnableRepositoryRegisterForeignApp()
{
return GetAppControl().IsMainApplication() ||
GetAppControl().IsStandaloneApplication() ||
GetAppControl().IsEssentialApplication() ||
GetAppControl().IsIsolatedApplication();
return GetAppSettings().IsMainApplication() ||
GetAppSettings().IsStandaloneApplication() ||
GetAppSettings().IsEssentialApplication() ||
GetAppSettings().IsIsolatedApplication();
}
bool CRepositoryService::EnableRepositoryLink()
{
return GetAppControl().IsIsolatedApplication() || GetAppControl().IsExternalApplication();
return GetAppSettings().IsIsolatedApplication() || GetAppSettings().IsExternalApplication();
}
#endif

View File

@@ -1,6 +1,18 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef VAPI_REPOSITORY_H
#define VAPI_REPOSITORY_H
#ifndef REPOSITORY_H
#define REPOSITORY_H
#include <list>
#include <map>
@@ -14,6 +26,7 @@
#include "module.h"
#include "object_lifetime_control.h"
#include "iso_monitor.h"
#include "app_config_file.h"
/**
* @brief repository service providing functionality to load modules, create objects and access exiting objects
@@ -248,6 +261,15 @@ public:
*/
void ResetConfigBaseline();
/**
* @brief Start components from a configuration.
* @remarks If the application runs as local application, the modules are loaded before starting the components.
* @param[in] rconfig Reference to the configuration file.
* @param[in] bAllowPartialLoad When set, allow partial loading the configuration (one or more components).
* @return Returns the load result.
*/
sdv::core::EConfigProcessResult StartFromConfig(const CAppConfigFile& rconfig, bool bAllowPartialLoad);
/**
* @brief Save the configuration of all components.
* @return The string containing all the components.
@@ -332,9 +354,14 @@ private:
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 Return the repository.
* @return Reference to the repository.
*/
CRepository& GetRepository();
/**
* @brief Repository service
@@ -362,16 +389,10 @@ public:
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
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.
@@ -391,7 +412,6 @@ public:
static bool EnableRepositoryLink();
};
DEFINE_SDV_OBJECT_NO_EXPORT(CRepositoryService)
#endif
DEFINE_SDV_OBJECT(CRepositoryService)
#endif // !define VAPI_REPOSITORY_H
#endif // !define REPOSITORY_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "sdv_core.h"
#include "toml_parser_util.h" // Insert to create the factory of this utility
#include <fstream>
@@ -21,120 +34,3 @@ CSDVCore& CSDVCore::GetInstance()
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

@@ -1,8 +1,22 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef CORE_H
#define CORE_H
#include <support/component_impl.h>
#include "app_control.h"
#include "app_settings.h"
#include "module_control.h"
#include "memory.h"
#include "repository.h"
@@ -27,11 +41,12 @@ public:
~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)
SDV_INTERFACE_CHAIN_MEMBER(GetAppControl())
SDV_INTERFACE_CHAIN_MEMBER(GetAppSettings())
SDV_INTERFACE_CHAIN_MEMBER(GetModuleControl())
SDV_INTERFACE_CHAIN_MEMBER(GetMemoryManager())
SDV_INTERFACE_CHAIN_MEMBER(GetRepository())
SDV_INTERFACE_CHAIN_MEMBER(GetLoggerControl())
END_SDV_INTERFACE_MAP()
/**
@@ -39,57 +54,6 @@ public:
* @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
};
/**
@@ -98,53 +62,4 @@ private:
*/
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

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#include "character_reader_utf_8.h"
#include "exception.h"

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#ifndef CHARACTER_READER_UTF_8_H
#define CHARACTER_READER_UTF_8_H

View File

@@ -0,0 +1,425 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "code_snippet.h"
#include "parser_node_toml.h"
/// The TOML parser namespace
namespace toml_parser
{
void CCodeSnippet::SetTokenList(const std::list<CToken>& rlstTokens)
{
m_lstTokens = rlstTokens;
m_bUseDefault = false;
}
void CCodeSnippet::SetTokenList(std::list<CToken>&& rlstTokens)
{
m_lstTokens = std::move(rlstTokens);
m_bUseDefault = false;
}
std::string CCodeSnippet::Compose(EComposeMode eMode, const CGenContext& rContext, size_t nAssignmentOffset /*= 0*/,
size_t nCommentOffset /*= 0*/) const
{
// Build the stream until the first comment.
std::stringstream sstream;
TTokenListIterator it = m_lstTokens.begin();
bool bCommaAvailable = false;
bool bPrintableCharsAvailable = false;
bool bLastTokenIsComment = false;
while (it != m_lstTokens.end() && it->Category() != ETokenCategory::token_comment)
{
// Add comma only when needed.
bool bSkip = false;
switch (it->Category())
{
case ETokenCategory::token_syntax_comma:
if (eMode != EComposeMode::compose_behind || !rContext.CommaNeeded() || bCommaAvailable)
{
bSkip = true;
break; // Only when placed behind a node
}
bCommaAvailable = true;
break;
case ETokenCategory::token_comment:
bSkip = !rContext.CommentAndNewlineAllowed();
if (!bSkip) bLastTokenIsComment = true;
break;
case ETokenCategory::token_syntax_new_line:
bSkip = !rContext.NewlineAllowed();
if (!bSkip) bLastTokenIsComment = false;
break;
default:
break;
}
if (!bSkip)
{
sstream << it->RawString();
bPrintableCharsAvailable = true;
}
++it;
}
// Determine the last comment.
TTokenListIterator itFirstComment = it;
TTokenListIterator itLastComment = it;
while (it != m_lstTokens.end())
{
if (it->Category() == ETokenCategory::token_comment)
itLastComment = it;
++it;
}
// Determine the code behind the last comment (and the obligatory newline).
TTokenListIterator itPostComment = itLastComment;
if (itPostComment != m_lstTokens.end())
++itPostComment;
if (itPostComment != m_lstTokens.end() && itPostComment->Category() == ETokenCategory::token_syntax_new_line)
++itPostComment;
// Stream the comment
if (!m_bCommentReplaced)
{
for (it = itFirstComment; it != itPostComment; ++it)
{
// Add comma only when needed.
bool bSkip = false;
switch (it->Category())
{
case ETokenCategory::token_syntax_comma:
if (eMode != EComposeMode::compose_behind || !rContext.CommaNeeded() || bCommaAvailable)
{
bSkip = true;
break; // Only when placed behind a node
}
bCommaAvailable = true;
break;
case ETokenCategory::token_comment:
bSkip = !rContext.CommentAndNewlineAllowed();
if (!bSkip) bLastTokenIsComment = true;
break;
case ETokenCategory::token_syntax_new_line:
bSkip = !rContext.NewlineAllowed();
if (!bSkip) bLastTokenIsComment = false;
break;
default:
break;
}
if (!bSkip)
{
sstream << it->RawString();
bPrintableCharsAvailable = true;
}
}
}
else
{
switch (eMode)
{
case EComposeMode::compose_inline:
case EComposeMode::compose_behind:
if (itFirstComment == m_lstTokens.end() && !m_ssComment.empty())
sstream << " ";
break;
case EComposeMode::compose_standalone_behind:
if (itFirstComment == m_lstTokens.end() && !m_ssComment.empty())
sstream << std::endl;
break;
default:
break;
}
// TODO: Align the comment....
size_t nPos = 0;
bool bEmptyLine = false;
while (nPos != std::string::npos)
{
// Find the chunk until the next newline or the end of the string
size_t nNextPos = m_ssComment.find('\n', nPos);
if (nNextPos != std::string::npos) nNextPos++;
if (nNextPos >= m_ssComment.size()) nNextPos = std::string::npos;
std::string ssChunk = m_ssComment.substr(nPos, nNextPos);
nPos = nNextPos;
// Insert an extra line break, but only when there isn't a line-break already...
if (!bEmptyLine && !sstream.str().empty())
{
switch (eMode)
{
case EComposeMode::compose_before:
case EComposeMode::compose_behind:
case EComposeMode::compose_inline:
sstream << std::string(nCommentOffset, ' ') << "#";
break;
default:
break;
}
sstream << std::endl;
}
if (ssChunk.find_first_not_of("\r\n") == std::string::npos)
bEmptyLine = true;
else
{
sstream << std::string(nCommentOffset, ' ') << "# " << ssChunk;
if (ssChunk.find('\n') == std::string::npos)
sstream << std::endl; // Comment is always followed by a newline.
bEmptyLine = false;
}
}
// TODO: Add spaces for next assignment
switch (eMode)
{
case EComposeMode::compose_inline:
if (itPostComment == m_lstTokens.end() && nAssignmentOffset)
sstream << std::string(nAssignmentOffset, ' ');
break;
case EComposeMode::compose_standalone_before:
if (itFirstComment == m_lstTokens.end() && !m_ssComment.empty())
sstream << std::endl;
break;
default:
break;
}
}
// Stream the rest of the tokens
for (it = itPostComment; it != m_lstTokens.end(); ++it)
{
bool bSkip = false;
switch (it->Category())
{
case ETokenCategory::token_syntax_comma:
if (eMode != EComposeMode::compose_behind || !rContext.CommaNeeded() || bCommaAvailable)
{
bSkip = true;
break; // Only when placed behind a node
}
bCommaAvailable = true;
break;
case ETokenCategory::token_comment:
bSkip = !rContext.CommentAndNewlineAllowed();
if (!bSkip) bLastTokenIsComment = true;
break;
case ETokenCategory::token_syntax_new_line:
bSkip = !rContext.NewlineAllowed();
if (!bSkip) bLastTokenIsComment = false;
break;
default:
break;
}
if (!bSkip)
{
sstream << it->RawString();
bPrintableCharsAvailable = true;
}
}
// Post processing
switch (eMode)
{
case EComposeMode::compose_behind:
// Was a comma provided?
if (!bCommaAvailable && rContext.CommaNeeded())
{
std::string ssTemp = sstream.str();
ssTemp.insert(0, bPrintableCharsAvailable ? "," : ", ");
sstream.str(ssTemp);
}
// Default newline needed
if (rContext.FinalNewline() && ((m_lstTokens.empty() && m_ssComment.empty()) || bLastTokenIsComment))
sstream << std::endl;
break;
case EComposeMode::compose_before:
//if (!bPrintableCharsAvailable && rContext.Embedded())
// sstream << " ";
break;
default:
break;
}
return sstream.str();
}
std::string CCodeSnippet::GetComment() const
{
if (m_bCommentReplaced)
return m_ssComment;
std::stringstream sstream;
size_t nNewLineCount = 0;
for (const CToken& rToken : m_lstTokens)
{
// Deal with new line.
if (rToken.Category() == ETokenCategory::token_syntax_new_line)
{
if (sstream.str().empty())
continue;
nNewLineCount++;
if (nNewLineCount < 2)
continue;
sstream << std::endl;
continue;
}
if (rToken.Category() == ETokenCategory::token_whitespace)
continue;
nNewLineCount = 0;
if (rToken.Category() == toml_parser::ETokenCategory::token_comment && !rToken.RawString().empty()
&& rToken.RawString()[0] == '#')
{
// The comment doesn't have a newline inside the comment text. This allows us to glue multiple comments to one
// large string.
// A new line will be inserted on the following conditions:
// - the line starts as comment, but only has whitespace.
// - there are at least some characters in the stream already.
// - the comment starts with a tab or multiple spaces
if (rToken.RawString().size() <= 1 || rToken.RawString().find_first_not_of(" \t", 1) == std::string::npos)
{
// Line has only whitespace
sstream << std::endl;
continue;
}
if (rToken.RawString()[1] == '\t' || rToken.RawString().substr(1, 2) == " "
|| rToken.RawString().substr(1, 2) == " \t")
{
// Line starts with a tab, are there any characters in the stream already.
if (!sstream.str().empty())
sstream << std::endl;
sstream << rToken.RawString().substr(1);
continue;
}
size_t nStartComment = rToken.RawString()[1] == ' ' ? 2 : 1;
if (!sstream.str().empty())
{
// The stream is not empty. Are there extra spaces (indicating a list or so)
if (rToken.RawString().substr(1, 2) == " ")
sstream << std::endl;
else // No newline, but space?
if (!std::isspace(sstream.str().back()))
sstream << " ";
}
// Add the content to the stream
sstream << rToken.RawString().substr(nStartComment);
}
}
// Trim space at the end of the string
std::string ssResult = sstream.str();
while (!ssResult.empty() && std::isspace(ssResult.back()))
ssResult.pop_back();
return ssResult;
}
void CCodeSnippet::SetComment(const std::string& rssComment)
{
// Just store the string. Further processing is done in the compose function.
m_ssComment = rssComment;
m_bCommentReplaced = true;
m_bUseDefault = false;
}
void CCodeSnippet::Clear()
{
m_ssComment.clear();
m_lstTokens.clear();
m_bCommentReplaced = false;
m_bUseDefault = true;
}
bool CCodeSnippet::HasCode() const
{
return !m_bUseDefault || (m_bCommentReplaced || !m_lstTokens.empty());
}
void CCodeSnippet::RemoveFormat()
{
// Read the comment from the token list.
if (!m_bCommentReplaced && !m_lstTokens.empty())
{
m_ssComment = GetComment();
m_bCommentReplaced = !m_ssComment.empty();
}
// Clear the token list
m_lstTokens.clear();
m_bUseDefault = !m_bCommentReplaced;
}
bool CCodeSnippet::HasComma() const
{
return !m_bCommentReplaced && (std::find_if(m_lstTokens.begin(), m_lstTokens.end(),
[](const CToken& rToken) { return rToken.Category() == ETokenCategory::token_syntax_comma; }) != m_lstTokens.end());
}
void CCodeSnippet::Insert(const CCodeSnippet& rCode)
{
// Something to do?
if (!rCode.HasCode()) return;
// Simple assignment?
if (!HasCode())
{
m_bCommentReplaced = rCode.m_bCommentReplaced;
m_ssComment = rCode.m_ssComment;
m_lstTokens = rCode.m_lstTokens;
return;
}
// Differentiate between comment text or token lists.
if (m_bCommentReplaced)
m_ssComment.insert(0, rCode.GetComment());
else
{
if (rCode.m_bCommentReplaced)
{
m_ssComment = rCode.GetComment() + GetComment();
m_bCommentReplaced = true;
}
else
m_lstTokens.insert(m_lstTokens.begin(), rCode.m_lstTokens.begin(), rCode.m_lstTokens.end());
}
}
void CCodeSnippet::Append(const CCodeSnippet& rCode)
{
// Something to do?
if (!rCode.HasCode()) return;
// Simple assignment?
if (!HasCode())
{
m_bCommentReplaced = rCode.m_bCommentReplaced;
m_ssComment = rCode.m_ssComment;
m_lstTokens = rCode.m_lstTokens;
return;
}
// Differentiate between comment text or token lists.
if (m_bCommentReplaced)
m_ssComment.append(rCode.GetComment());
else
{
if (rCode.m_bCommentReplaced)
{
m_ssComment = rCode.GetComment() + GetComment();
m_bCommentReplaced = true;
}
else
m_lstTokens.insert(m_lstTokens.end(), rCode.m_lstTokens.begin(), rCode.m_lstTokens.end());
}
}
} // namespace toml_parser

View File

@@ -0,0 +1,220 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef CODE_SNIPPET_H
#define CODE_SNIPPET_H
#include <list>
#include <string>
#include <memory>
#include <interfaces/toml.h>
#include "lexer_toml.h"
/// The TOML parser namespace
namespace toml_parser
{
// Forward declaration
class CGenContext;
/**
* @brief Comment or code snippet structure.
* @details Each node has multiple code snippets used to reproduce the exact code. For example with an assignment:
* @code
*
* # This is out of scope comment before
*
* # This is comment before
* var_a = "abc" # comment behind
* # more comment behind
*
* # This is out of scope comment behind
*
* @endcode
*
* The code snippets are identified as follows:
* @code
* <out of scope comment before>
* <comment before>
* <space before><key><space>=<space><value><comment behind>
* <out of scope comment behind>
* @endcode
*
* Each code snippet has token a list and comment string.
* - If the tokenlist is not empty and comment string is empty, the tokenlist determines the comment.
* - If the tokenlist is empty, the comment string determines the comment. The following steps are taken during the
* GetComment with the raw flag enabled:
* - For the comment behind, the current position for the comment is determined by examining the length of the sibling
* nodes. If the position is larger than 80, the start position is either 80 or one space character more than the
* node length.
* - For all other comment types, the start position is zero.
* - Stream spaces until the start position is reached; then stream the comment character '#' followed by a space.
* - The comment text is streamed word by word. If between the words a space exists, this can be replaced by a
* newline when the current position with the new word would exceed column 132. After the new line spaces fill up
* until the start position. Then the comment character and a space is streamed followed by more words from the
* comment.
* - If within the comment a newline is available, this is followed by an additional newline within the raw comment
* stream. Also if multiple empty lines occur (lines with no character sor only whitespace), this is also
* streamed with one additional newline at the beginning.
* - The last word is followed by a newline.
* - If tokenlist is not empty and comment string is not empty, the comments plus the newline following within the
* tokenlist are replaced by the new comment text like described above, with the exception that the start position is
* determined by the current position within the tokenlist.
*/
class CCodeSnippet
{
public:
/**
* @brief Set the token list containing the code for this code snippet.
* @param[in] rlstTokens Reference to the token list to be set.
*/
void SetTokenList(const std::list<CToken>& rlstTokens);
/**
* @brief Set the token list containing the code for this code snippet.
* @param[in] rlstTokens Reference to the token list to be set.
*/
void SetTokenList(std::list<CToken>&& rlstTokens);
/**
* @brief Mode the code snippet composer should run in.
*/
enum class EComposeMode
{
compose_inline, ///< Compose as inline whitespace and comment. If there is no token list and no comment
///< string, compose as one space. If there is only a comment string, insert a space, add
///< the comment followed by an obligatory newline, and insert spaces until the next
///< provided position. If there are tokens with a comment token, replace the comment. If
///< there are tokens without comment, add the comment, newline and spaces.
compose_before, ///< Compose as comment assigned to and located before the node. If there is no token list
///< and no comment string, doesn't add anything. If there is only a comment string, adds
///< the comment followed by the obligatory newline. If there are tokens with a comment
///< token, replace the comment. If there are tokens without the comment, place the comment
///< before the last newline or when not available, at the end of the tokens followed by a
///< new newline.
compose_behind, ///< Compose as comment assigned to and located behind the node. If there is no token list
///< and no comment string, add a newline. If there is a comment string and no tokens,
///< add a space, the comment string followed by the obligatory newline. If there is a token
///< list without comment, add a comment before the newline or at the end with an additional
///< newline.
compose_standalone_before, ///< Compose as stand-alone comment before the node. Replace any token list if a comment
///< string is available.
compose_standalone_behind, ///< Compose as stand-alone comment behind the node. Replace any token list if a comment
///< string is available.
};
/**
* @brief Compose a conde string from the stored tokens and/or string.
* @details The comment text to code translation is as follows:
* @code
* The following text might be split into multiple TOML comment lines.
* @endcode
* @code
* # The following text might be split into
* # multiple TOML comment lines.
* @endcode
* and
* @code
* This is part 1 of a text which will be as two separated blocks.
* This is part 2 of a text which will be as two separated blocks.
* @endcode
* @code
* # This is part 1 of a text which will be as
* # two separated blocks.
* #
* # This is part 2 of a text which will be as
* # two separated blocks.
* @endcode
* and
* @code
* This is a list or an indented text, which is streated as a list.
* First indented text which belongs as a separated line.
* Second indented text which belongs as a separate line.
* @endcode
* @code
* # This is a list or an indented text, which is
* # streated as a list.
* # First indented text which belongs as a
* # separated line.
* # Second indented text which belongs as a
* # separate line.
* @endcode
* @param[in] eMode The mode the composer should run in.
* @param[in] rContext Reference to the node context used for the code generation.
* @param[in] nAssignmentOffset The offset for a next assignent; only used for inline composition.
* @param[in] nCommentOffset The offset to insert a multi-line comment; only used for inline and behind composition.
* @return The composed code string.
*/
std::string Compose(EComposeMode eMode, const CGenContext& rContext, size_t nAssignmentOffset = 0, size_t nCommentOffset = 0) const;
/**
* @brief Get the stored comment from the code snippet. A stored comment string supersedes a comment string from the
* token list. Comment lines that belong together are combined together as a text without line-breaks.
* @return Returns the stored comment string.
*/
std::string GetComment() const;
/**
* @brief Set a comment string. The comment will be formatted in the compose function. Lines that should stay together
* should not be separated by a line-break. Comments should be provided without the TOML comment character '#'.
* @param[in] rssComment Reference to the string containing the comment text.
*/
void SetComment(const std::string& rssComment);
/**
* @brief Remove the content of the code snippet.
*/
void Clear();
/**
* @brief Does the snippet contain code?
* @return Returns whether the snippet has code or not.
*/
bool HasCode() const;
/**
* @brief This function will remove the formatting from the code.
*/
void RemoveFormat();
/**
* @brief Does the code snippet have a comma in the token list?
* @return Returns whether the token list is active and contains a comma token.
*/
bool HasComma() const;
/**
* @brief Insert a code snippet before the existing comment.
* @remarks In case the code has been replaced by a comment text of the current or the to be inserted code snippet, the
* result will be a combined comment text, disregarding the format.
* @param[in] rCode Reference to the code snippet to insert before the current code.
*/
void Insert(const CCodeSnippet& rCode);
/**
* @brief Append a code snippet behind the existing comment.
* @remarks In case the code has been replaced by a comment text of the current or the to be inserted code snippet, the
* result will be a combined comment text, disregarding the format.
* @param[in] rCode Reference to the code snippet to appended behind the current code.
*/
void Append(const CCodeSnippet& rCode);
private:
std::list<CToken> m_lstTokens; ///< Token list for the code snippet in raw format.
std::string m_ssComment; ///< The comment text for the code snippet in text format.
bool m_bCommentReplaced = false; ///< Comment replaced with SetComment function.
bool m_bUseDefault = false; ///< When set, the format was removed from the code.
};
} // namespace toml_parser
#endif // !defined CODE_SNIPPET_H

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#ifndef CONFIG_EXCEPTION_H
#define CONFIG_EXCEPTION_H

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#include "lexer_toml.h"
#include <interfaces/toml.h>
#include "exception.h"
@@ -176,7 +190,15 @@ namespace toml_parser
}
}
void CLexer::Reset()
void CLexer::Clear()
{
m_lstTokens.clear();
m_itCursor = m_lstTokens.end();
while (!m_stackExpectations.empty())
m_stackExpectations.pop();
}
void CLexer::ResetCursor()
{
m_itCursor = m_lstTokens.begin();
}
@@ -275,6 +297,54 @@ namespace toml_parser
void CLexer::SmartExtendNodeRange(CNodeTokenRange& rTokenRange) const
{
/*
Consider the following TOML code:
# Out of scope comment
# Pre comment with spaces before
# Pre comment
[table] # Post comment
# Post comment without indentation
# Out of scope comment
# Pre comment
var = "1234" # Post comment
# More post comment with indentation
# Pre comment of var2 due to indentation identical or smaller then var2 def indentation
var2 = "5678"
# Pre comment of var3
var3 = 9012 # Post comment of var3
# More post comment of var3 (due ot space at the beginning)
# Pre comment of var4 due to lesser indentation
# Pre comment of var4 due to previous pre comment
# Pre comment of var4
var4 = 3456
# Out of scope comment due to missing post comment at same line as defintion
var5 = [ 8765, # Post comment for var5[0] (event behind the comma)
# Pre comment for var[1]
4321 ] # Post comment for var5
var6 = 7890 # Post comment of var6
# Out of scope comment due to missing indentation
--------------------------------------------
The function uses as input the token range of the definition and extends the definition with comments and whitespace that
belong to the definition. Therefore the following rules count:
- Pre comments are defined on the lines before if they are covering the complete line and do not have an indentation larger
than the indentation of the definition.
- Post comments are defined at the same line following the definition.
- If a comment with indentation is supplied before the definition, it is a pre-comment if there is no post-comment defined
that connects to this comment. Otherwise the comment belongs to the definition before.
- If comments follow on the lines directly behind the definition, they are post-comments when the comment has started at
the same line as the definition and each comment has an indentation larger than the indentation of the next definition.
- The extended range always includes the complete line including the indentation before and the line-break behind.
*/
// Define an iterator range type.
using TRange = std::pair<TTokenListIterator, TTokenListIterator>;
@@ -285,7 +355,7 @@ namespace toml_parser
// return A pair containing the begin and one past the end of the line.
auto fnGetLine = [this](const TTokenListIterator& rit) -> TRange
{
// When the end of the list is reached, there is no more line.
// When the end of the list is reached, there are no more tokens in the line.
if (rit == m_lstTokens.end())
return std::make_pair(m_lstTokens.end(), m_lstTokens.end());
@@ -392,6 +462,26 @@ namespace toml_parser
return bComments;
};
// Check a range for a comment at the end (as last token or just before the line break).
// remarks No validity check is done for the supplied token iterator.
// param[in] rprRange Pair with the start position and one past the end position of a range.
// return Returns whether the line contains a post comment.
auto fnPostComment = [this](const TRange& rprRange) -> bool
{
auto itToken = rprRange.second;
if (itToken == rprRange.first)
return false;
--itToken;
if (itToken->Category() == ETokenCategory::token_comment)
return true;
if (itToken->Category() != ETokenCategory::token_syntax_new_line)
return false;
if (itToken == rprRange.first)
return false;
--itToken;
return itToken->Category() == ETokenCategory::token_comment;
};
// Check a range for empty line(s) only (ignore whitespace and newlines).
// remarks No validity check is done for the supplied token iterator.
// param[in] rprRange Pair with the start position and one past the end position of a range.
@@ -530,75 +620,127 @@ namespace toml_parser
// The beginning and the end cannot be the same, except for an empty range.
if (itTokenBegin == itTokenEnd) return;
// Determine line boundaries of the current range
// Determine whether there is a post comment at all
auto itTokenBeginLine = itTokenBegin == m_lstTokens.begin() ? itTokenBegin : fnGetLine(itTokenBegin).first;
auto itTokenEndLine = itTokenEnd;
--itTokenEndLine;
itTokenEndLine = fnGetLine(itTokenEndLine).second;
// Are the comments following the statement?
bool bCommentsSameLine = fnCommentsOnly(std::make_pair(itTokenEnd, itTokenEndLine));
bool bPostCommentPresent = fnCommentsOnly(std::make_pair(itTokenEnd, itTokenEndLine));
// Is there only whitespace before or after the statement
bool bWhitespaceBefore = fnEmptyLine(std::make_pair(itTokenBeginLine, itTokenBegin)) && !bIsParent;
bool bWhitespaceAfter = fnEmptyLine(std::make_pair(itTokenEnd, itTokenEndLine));
bool bWhitespaceBefore = fnEmptyLine(TRange{itTokenBeginLine, itTokenBegin}) && !bIsParent;
bool bWhitespaceAfter = fnEmptyLine(TRange{itTokenEnd, itTokenEndLine});
// Get the indentation length of the line (whether there is other comments before or not).
size_t nIndentLen = fnGetIndentation(std::make_pair(itTokenBeginLine, itTokenEnd));
// Post comments or whitespace until the end of the line definitely belongs to the extended range.
if (bPostCommentPresent || bWhitespaceAfter) itTokenEnd = itTokenEndLine;
// Extend the range to include the beginning of the line and the end of the line.
TRange prExtended = std::make_pair(bWhitespaceBefore ? itTokenBeginLine : itTokenBegin,
(bCommentsSameLine || bWhitespaceAfter) ? itTokenEndLine : itTokenEnd);
// Get the indentation length of the definition.
size_t nDefIndent = fnGetIndentation(std::make_pair(itTokenBeginLine, itTokenEnd));
// Deal with whitespace and optionally comments before
TRange prLine = prExtended;
bool bEmptyLineBefore = false;
if (bWhitespaceBefore)
// Classify the lines
struct SCommentLine
{
// Check for comments preceeding the range. If previous lines are having comments only (and optionally whitespace), and
// the indentation is identical or less than the indentation of the range, the comment belongs to the range.
while (fnGetPrevLine(prLine) && fnCommentsOnly(prLine) && fnGetIndentation(prLine) <= nIndentLen)
prExtended.first = prLine.first;
TRange tLine;
size_t nIndent;
};
std::list<SCommentLine> lstLinesBefore, lstLinesBehind;
// Check whether there is an empty line before the range or the range starts at the first token in the list.
bEmptyLineBefore = prLine.first != prExtended.first ? fnEmptyLine(prLine) : prLine.first == m_lstTokens.begin();
}
// Deal with whitespace and optionally comments following
if (bWhitespaceAfter)
// Classify the lines before if the definition doesn't have other definition parts before at the same line.
TRange tLine = {itTokenBeginLine, itTokenEndLine};
bool bIncludeAllBefore = false;
while (bWhitespaceBefore && tLine.first != m_lstTokens.begin())
{
// Check for comments following the range. But only when there are comments at the same line and the indentation of the
// next line is larger than the range indentation or there is an empty line following.
TRange prPotential = prExtended;
bool bUsePotential = false;
prLine = prExtended;
while (bCommentsSameLine && fnGetNextLine(prLine) && fnCommentsOnly(prLine))
if (!fnGetPrevLine(tLine))
{
if (bUsePotential || fnGetIndentation(prLine) > nIndentLen)
{
bUsePotential = true;
prPotential.second = prLine.second;
}
else
prExtended.second = prLine.second;
bIncludeAllBefore = true;
break;
}
// Check whether there is an empty line following the range or the range ends at the end of the list.
bool bEmptyLineBeyond = prLine.second == m_lstTokens.end() ? true : fnEmptyLine(prLine);
// If an empty line is following and a potential extension was detected, extend the range
if (bEmptyLineBeyond && bUsePotential)
prExtended.second = prPotential.second;
// If there is an empty line before, include any empty lines until the next token or the end of the list.
while (bEmptyLineBefore && bEmptyLineBeyond && fnGetNextLine(prLine) && fnEmptyLine(prLine))
prExtended.second = prLine.second;
if (fnEmptyLine(tLine))
{
bIncludeAllBefore = true;
break;
}
if (!fnCommentsOnly(tLine))
{
// Check for a post comment
bIncludeAllBefore = !fnPostComment(tLine);
break;
}
lstLinesBefore.emplace_front(SCommentLine{tLine, fnGetIndentation(tLine)});
}
rTokenRange.ExtendedNode(CTokenRange(fnToken(prExtended.first), fnToken(prExtended.second)));
// Classify the lines behind (when there is a post comment or only whitespace following).
size_t nNextStatementIndent = 0;
tLine = {itTokenBeginLine, itTokenEndLine};
bool bIncludeAllBehind = false;
while ((bPostCommentPresent || bWhitespaceAfter) && tLine.second != m_lstTokens.end())
{
if (!fnGetNextLine(tLine))
{
bIncludeAllBehind = true;
break;
}
if (fnEmptyLine(tLine))
{
// Note: one empty line still belongs to the extended range.
lstLinesBehind.emplace_back(SCommentLine{tLine, 0});
bIncludeAllBehind = true;
break;
}
size_t nIndent = fnGetIndentation(tLine);
if (!fnCommentsOnly(tLine))
{
nNextStatementIndent = nIndent;
break;
}
lstLinesBehind.emplace_back(SCommentLine{tLine, fnGetIndentation(tLine)});
}
TRange tExtended{itTokenBegin, itTokenEnd};
if (!lstLinesBefore.empty())
{
// Include all comments before, or only the comments with an indentation identical or smaller than the def indentation.
if (bIncludeAllBefore)
tExtended.first = lstLinesBefore.front().tLine.first;
else
{
auto it = lstLinesBefore.crbegin();
while (it != lstLinesBefore.crend())
{
// NOTE: Use of SCommentLine structure here, prevents unusedStruct warning of cppcheck
const SCommentLine& rsLine = *it;
if (rsLine.nIndent > nDefIndent)
break;
tExtended.first = rsLine.tLine.first;
++it;
}
}
}
if (!lstLinesBehind.empty())
{
// Include all comments behind, or only the comments with an indentation larger than the indentation of the next def.
// The latter only when there is a post-comment.
if (bIncludeAllBehind)
tExtended.second = lstLinesBehind.back().tLine.second;
else if (bPostCommentPresent)
{
auto it = lstLinesBehind.cbegin();
while (it != lstLinesBehind.cend())
{
// NOTE: Use of SCommentLine structure here, prevents unusedStruct warning of cppcheck
const SCommentLine& rsLine = *it;
if (rsLine.nIndent <= nNextStatementIndent)
break;
tExtended.second = rsLine.tLine.second;
++it;
}
}
}
rTokenRange.ExtendedNode(CTokenRange(fnToken(tExtended.first), fnToken(tExtended.second)));
// Determine if there are any more nodes before the end of the list.
auto itFinal = prExtended.second;
auto itFinal = tExtended.second;
bool bNextNodeFound = false;
while (itFinal != m_lstTokens.end() && !bNextNodeFound)
{
@@ -1161,7 +1303,7 @@ namespace toml_parser
{
token = CToken(ETokenCategory::token_syntax_array_close);
m_stackExpectations.pop();
if (m_stackExpectations.top() == EExpectation::expect_value_once)
if (!m_stackExpectations.empty() && m_stackExpectations.top() == EExpectation::expect_value_once)
m_stackExpectations.pop();
}
else
@@ -1184,7 +1326,7 @@ namespace toml_parser
rReader.Consume();
token = CToken(ETokenCategory::token_syntax_inline_table_close);
m_stackExpectations.pop();
if (m_stackExpectations.top() == EExpectation::expect_value_once)
if (!m_stackExpectations.empty() && m_stackExpectations.top() == EExpectation::expect_value_once)
m_stackExpectations.pop();
break;
case ',':

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#ifndef LEXER_TOML_H
#define LEXER_TOML_H
@@ -11,6 +25,32 @@
/// The TOML parser namespace
namespace toml_parser
{
/**
* @brief Safe stack implementation for an enum returning a default value when the stack is empty.
*/
template <typename TEnum, TEnum TDefault>
class enum_stack : public std::stack<TEnum>
{
public:
/**
* @brief Pop the top-most entry from the stack if the stack is not empty.
*/
void pop()
{
if (!std::stack<TEnum>::empty())
std::stack<TEnum>::pop();
}
/**
* @brief Return the value of the top-most entry of the stack or if empty a default value.
* @return The top-most or default value.
*/
TEnum top() const
{
return std::stack<TEnum>::empty() ? TDefault : std::stack<TEnum>::top();
}
};
/**
* @brief Node token range used to regenerate the source from the node entries.
* @details A node can have several token ranges identifying code that belongs to the node or precedes or succeeds the node. The
@@ -166,10 +206,15 @@ namespace toml_parser
*/
void Feed(const std::string& rssString, bool bValueOnly = false);
/**
* @brief Clear the lexer content.
*/
void Clear();
/**
* @brief Reset the lexer cursor position.
*/
void Reset();
void ResetCursor();
/**
* @brief Navigation modes supported by the lexer.
@@ -362,11 +407,13 @@ namespace toml_parser
*/
enum class EExpectation
{
undefined, ///< Error case when code is supplied that was not expected.
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
enum_stack<EExpectation, EExpectation::undefined>
m_stackExpectations; ///< Tracking of key or value expectations in nested structures
const std::vector<std::string>
m_vecKeyDelimiters{"\n", "\t", "\r", " ", "", ".", "=", "]"}; ///< Characters that delimit a key

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#include "lexer_toml_token.h"
#include <sstream>

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#ifndef LEXER_TOML_TOKEN_H
#define LEXER_TOML_TOKEN_H
@@ -310,7 +324,7 @@ namespace toml_parser
union
{
std::string m_ssContentString; ///< Token string content (used with keys, strings and errors).
std::string m_ssContentString; ///< Token string contentv (used with keys, strings and errors).
int64_t m_iContentInteger; ///< Token integer content
double m_dContentFloatingpoint; ///< Token floatingpoint content
bool m_bContentBoolean; ///< Token boolean content

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#include "miscellaneous.h"
#include "exception.h"
#include <sstream>

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#ifndef MISCELLANEOUS_H
#define MISCELLANEOUS_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#ifndef PARSER_NODE_TOML_H
#define PARSER_NODE_TOML_H
@@ -9,10 +23,9 @@
#include <list>
#include <map>
#include <interfaces/toml.h>
#include <support/interface_ptr.h>
#include "lexer_toml.h"
#include "miscellaneous.h"
#include "code_snippet.h"
/// The TOML parser namespace
namespace toml_parser
@@ -30,9 +43,11 @@ namespace toml_parser
*/
enum class EGenerateOptions : uint32_t
{
inline_when_possible = 0x01, ///< Try to generate as much as possible as inline nodes.
explicit_when_possible = 0x02, ///< Try to generate as much as possible as explicit nodes.
no_comments = 0x10, ///< Do not include comments
inline_when_possible = 0x01, ///< Try to generate as much as possible as inline nodes.
explicit_when_possible = 0x02, ///< Try to generate as much as possible as explicit nodes.
no_comments = 0x10, ///< Do not include comments.
reduce_whitespace = 0x20, ///< Add comments, but reduce extra newlines before and after the node.
full_header = 0x40, ///< When generating tables or table arrays, include the header in generated code.
};
/**
@@ -50,7 +65,8 @@ namespace toml_parser
CGenContext(const std::string& rssPrefixKey = std::string(), uint32_t uiOptions = 0);
/**
* @brief Called by the node that is generating the TOML. If not initialized before, initializes with the provided node.
* @brief Called by the node that is generating the TOML. If not initialized before, extract the context from the node and
* assign this node as top node for the code generation.
* @param[in] rptrNode Reference to the node that could be used for initialization as top most node.
*/
void InitTopMostNode(const std::shared_ptr<const CNode>& rptrNode);
@@ -65,9 +81,12 @@ namespace toml_parser
/**
* @brief Create a copy of the context class with a new key context.
* @param[in] rssNewKeyContext Reference to the string containing the new key context.
* @param[in] rptrNode Reference to the node pointer to extract the context from.
* @param[in] bLastNode When set, this is the last node in the current view.
* @return The copy of the contetx class.
*/
CGenContext CopyWithContext(const std::string& rssNewKeyContext) const;
CGenContext CopyWithContext(const std::string& rssNewKeyContext, const std::shared_ptr<CNode>& rptrNode,
bool bLastNode) const;
/**
* @brief Get the stored prefix key that should be used for the TOML code generation.
@@ -81,6 +100,24 @@ namespace toml_parser
*/
const std::string& KeyContext() const;
/**
* @brief The key path composed of the prefix and the relative key path.
* @return Reference to the key path string.
*/
const std::string& KeyPath() const;
/**
* @brief The key path composed of the key kontext and the relative key path.
* @return Reference to the key path string.
*/
const std::string& FullKeyPath() const;
/**
* @brief The relative key path, relative to the current context.
* @return Reference to the key path string.
*/
const std::string& RelKeyPath() const;
/**
* @brief Is this the top most node?
* @return Returns when the node is the top most node.
@@ -100,17 +137,115 @@ namespace toml_parser
*/
bool CheckOption(EGenerateOptions eOption) const;
/**
* @brief Is the last-node-flag set?
* @return Returns whether the last-node-flag has been set indicating the node using this context to be the last node within
* the current view.
*/
bool LastNode() const;
/**
* @brief Node presentation form.
*/
enum class EPresentation
{
standard, ///< Standard presentation (root or within table/table-array)
standard_inline, ///< Inline presentation (root or within table/table-array)
embedded, ///< Embedded presentation (within array or inline-table)
};
/**
* @brief Get the presentation form of the node.
* @return The node presentation extracted from the node and the generation context.
*/
EPresentation Presentation() const;
/**
* @brief Is the node a standard node?
* @return Returns 'true' when the node is a standard node.
*/
bool Standard() const;
/**
* @brief Is the node an inline node?
* @remarks Embedded nodes are also inline.
* @return Returns 'true' when the node is an inline node.
*/
bool Inline() const;
/**
* @brief Is the node an embedded node (within an inline table or array)?
* @return Returns 'true' when the node is an embedded node.
*/
bool Embedded() const;
/**
* @brief Does the node need an assignment (key and when inline, equal sign)?
* @remarks Embedded nodes within an array do not need an assignment.
* @return Returns 'true' when the node needs an assignment.
*/
bool Assignment() const;
/**
* @brief For an embedded node, is a comma indicating the next node needed?
* @remarks Some inline arrays can have a final comma behind the last embedded node.
* @return Returns 'true' when a comma is needed.
*/
bool CommaNeeded() const;
/**
* @brief Are comments and newlines allowed? For an embedded node, this might be prohibited. But can also explicitly be
* defined in the context.
* @remarks Inline tables require a one line definition for the embedded nodes.
* @return Returns 'true' if comments and newlines are allowed.
*/
bool CommentAndNewlineAllowed() const;
/**
* @brief Are newlines allowed? For an embedded node, this might be prohibited. But can also explicitly be defined in the
* context.
* @remarks Inline tables require a one line definition for the embedded nodes.
* @return Returns 'true' if comments and newlines are allowed.
*/
bool NewlineAllowed() const;
/**
* @brief For a standard (inline) node, is a newline required at the end of the node definition?
* @returns Returns'true' if a newline is required behind the node definition.
*/
bool FinalNewline() const;
private:
std::shared_ptr<const CNode> m_ptrTopMostNode; ///< Top most node that is used for the generation. The parent nodes of
///< the top most node will not be part of the node generation and if
///< they contain child nodes in their view, the nodes are printed by
///< their parent and not by their view.
std::string m_ssPrefixKey; ///< Prefix key to be used during the generation of the TOML code.
std::string m_ssKeyContext; ///< string containing the current context. The string must follow the
///< key rules for separation with bare, literal and quoted keys.
uint32_t m_uiOptions = 0; ///< Zero or more options to take into account when creating the text to
///< the TOML nodes.
bool m_bTopMost = true; ///< Set when this context is the top most context.
void ExtractContext(const std::shared_ptr<const CNode>& rptrNode);
std::shared_ptr<const CNode> m_ptrTopMostNode; ///< Top most node that is used for the generation. The
///< parent nodes of the top most node will not be part of
///< the node generation and if they contain child nodes in
///< their view, the nodes are printed by their parent and
///< not by their view.
std::string m_ssPrefixKey; ///< Prefix key to be used during the generation of the TOML
///< not by their view.
std::string m_ssKeyContext2;
std::string m_ssKeyContext; ///< String containing the current context. The string must
///< follow the key rules for separation with bare, literal
///< and quoted keys.
std::string m_ssKeyPath; ///< The key path composed of the prefix and the relative
///< key path.
std::string m_ssFullKeyPath; ///< The full key path composed of the key context and the
///< relative key path.
std::string m_ssRelKeyPath; ///< The relative key path, relative to the current context.
uint32_t m_uiOptions = 0; ///< Zero or more options to take into account when creating
///< the text to the TOML nodes.
bool m_bTopMost = true; ///< Set when this context is the top most context.
bool m_bLastNode = false; ///< Is this the last node in the current view?
bool m_bFinalLastNode = false; ///< When set, this is the last (top level) node of the node
///< hierarchy. Only child nodes can still follow.
EPresentation m_ePresentation = EPresentation::standard; ///< Presentation of the node.
bool m_bOneLine = false; ///< Set when the node is not allowed to cover more than
///< one line (except when using multi-line strings).
bool m_bAssignment = false; ///< Does the node need an assignment.
bool m_bCommaNeeded = false; ///< Is a comma needed following the node definition?
bool m_bFinalNewline = false; ///< Is a final newline behind the definition required?
};
/**
@@ -118,7 +253,7 @@ namespace toml_parser
*/
class CNode :
public std::enable_shared_from_this<CNode>, public sdv::IInterfaceAccess, public sdv::toml::INodeInfo,
public sdv::toml::INodeDelete, public sdv::toml::INodeUpdate
public sdv::toml::INodeUpdate
{
protected:
/**
@@ -150,7 +285,6 @@ namespace toml_parser
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::toml::INodeInfo)
SDV_INTERFACE_ENTRY(sdv::toml::INodeDelete)
SDV_INTERFACE_ENTRY(sdv::toml::INodeUpdate)
END_SDV_INTERFACE_MAP()
@@ -194,8 +328,9 @@ namespace toml_parser
virtual sdv::any_t GetValue() const override;
/**
* @brief Get the index of this node within the parent collection. Overload of sdv::toml::INodeInfo::GetIndex.
* @return The index of the node within the parent collection node or npos when no parent is available.
* @brief Get the index of this node within the view collection (either the assigned view or the parent). Overload of
* sdv::toml::INodeInfo::GetIndex.
* @return The index of the node within the view collection node or npos when no parent is available.
*/
virtual uint32_t GetIndex() const override;
@@ -214,27 +349,25 @@ namespace toml_parser
/**
* @brief Set or replace a comment for the node. Overload of sdv::toml::INodeInfo::SetComment.
* @remarks This function can also be used to insert whitespace (with or without comments) when used in raw mode.
* Set the comment text for the node. If a comment is proided as text (normal behavior), the comment text will be
* formatted automatically when generating the TOML text. If the comment is provided as raw comment, the text should
* contain all whitespace and the comment '#' character before the comment text.
* Comments inserted before the enode will be inserted on the line before the node uness the comment is provided in raw
* format and is ended with a newline and optionally whitespace. Comment inserted behind the node will be inserted on
* @details Set the comment text for the node. If a comment is provided as text (normal behavior), the comment text will
* be formatted automatically when generating the TOML text. If the comment text should not contain the comment
* character '#' before the comment text.
* Comments inserted before the node will be inserted on the line before the node unless the comment is provided in raw
* format and is ended with a line-break and optionally whitespace. Comment inserted behind the node will be inserted on
* the same line as the node.
* Comments provided as text is automatically wrapped to 80 characters if possible. Newlines in the text will cause a
* new comment line to start.
* @param[in] ssComment String containing the comment text or the raw comment string to set.
* @param[in] uiFlags One or more ECommentFlags flags influencing the behavior of the comment.
* Comments provided as text is automatically wrapped to 132 characters if possible. Line-breaks in the text will cause
* a new comment line to start.
* @param[in] eType The comment type to set the comment text for.
* @param[in] ssComment String containing the comment text to set.
*/
virtual void SetComment(const sdv::u8string& ssComment, uint32_t uiFlags) override;
virtual void SetComment(sdv::toml::INodeInfo::ECommentType eType, const sdv::u8string& ssComment) override;
/**
* Get the current comment for the node. Overload of sdv::toml::INodeInfo::GetComment.
* @remarks To receive the whitespace formatting the node, use this function in raw mode.
* @param[in] uiFlags One or more ECommentFlags flags identifying the string format of the comment to return.
* @param[in] eType The comment type to get the comment text of.
* @return String with the comment text or an empty string if no comment is available.
*/
virtual sdv::u8string GetComment(uint32_t uiFlags) override;
virtual sdv::u8string GetComment(sdv::toml::INodeInfo::ECommentType eType) override;
/**
* @brief Format the node automatically. This will remove the whitespace between the elements within the node. Comments
@@ -242,6 +375,18 @@ namespace toml_parser
*/
virtual void AutomaticFormat() override;
/**
* @brief Is the node inline? Overload of sdv::toml::INodeInfo::IsInline.
* @return Returns whether the node is defined as inline node.
*/
virtual bool IsInline() const override;
/**
* @brief Is the node defined as standard node? Overload of sdv::toml::INodeInfo::IsStandard.
* @return Returns whether the node is defined as standard node.
*/
virtual bool IsStandard() const override;
/**
* @brief Update the node with TOML code information. The default implementation takes the comment and whitespace around the
* node and stores this for node reconstruction.
@@ -249,19 +394,6 @@ namespace toml_parser
*/
virtual void UpdateNodeCode(const CNodeTokenRange& rNodeRange);
/**
* @brief Delete the current node. Overload of sdv::toml::INodeDelete::DeleteNode.
* @attention A successful deletion will cause all interfaces to the current node to become inoperable.
* @return Returns whether the deletion was successful.
*/
virtual bool DeleteNode() override;
/**
* @brief Is this node marked as deleted?
* @return Returns whether this node has been deleted.
*/
bool IsDeleted() const;
/**
* @brief Change the key name of the node (if the node is not a value node of an array). Overload of
* sdv::toml::INodeUpdate::ChangeName.
@@ -275,7 +407,7 @@ namespace toml_parser
/**
* @brief Change the value of the node. Overload of sdv::toml::INodeUpdate::ChangeValue.
* @remarks Only valid for value nodes. Changing the value type is not supported.
* @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a
* @param[in] anyNewValue The value of the node, being either an integer, floating point number, virtual bool value or a
* string. Conversion is automatically done to int64, double float, bool or u8string.
* @return Returns whether the value change was successful.
*/
@@ -297,6 +429,19 @@ namespace toml_parser
*/
virtual bool MoveDown() override;
/**
* @brief Delete the current node. Overload of sdv::toml::INodeUpdate::DeleteNode.
* @attention A successful deletion will cause all interfaces to the current node to become inoperable.
* @return Returns whether the deletion was successful.
*/
virtual bool DeleteNode() override;
/**
* @brief Is this node marked as deleted?
* @return Returns whether this node has been deleted.
*/
bool IsDeleted() const;
/**
* @brief Do a dynamic cast to one of the base types of the node.
* @return Casted shared pointer to the base type if the type is valid, or an empty pointer if not.
@@ -311,19 +456,19 @@ namespace toml_parser
template <typename TNodeType>
std::shared_ptr<const TNodeType> Cast() const;
/**
* @brief Gets the parent node pointer
* @return Returns the parent node pointer or an empty pointer when no parent was assigned or the stored weak pointer could
* not be locked.
*/
std::shared_ptr<CNodeCollection> GetParentPtr() const;
/**
* @brief Set the parent node.
* @param[in] rptrParent Reference to the node to assign to this node as a parent.
*/
void SetParentPtr(const std::shared_ptr<CNodeCollection>& rptrParent);
/**
* @brief Gets the parent node pointer.
* @return Returns the parent node pointer or an empty pointer when no parent was assigned or the stored weak pointer could
* not be locked.
*/
std::shared_ptr<CNodeCollection> GetParentPtr() const;
/**
* @brief Get the parent path of the node.
* @return Return the parent path if existining and not a root.
@@ -337,6 +482,12 @@ namespace toml_parser
*/
void SetViewPtr(const std::shared_ptr<CNodeCollection>& rptrView);
/**
* @brief Gets the view definition node pointer.
* @return Returns the store view definition node pointer or an empty pointer when no view was assigned.
*/
std::shared_ptr<CNodeCollection> GetViewPtr() const;
/**
* @brief Checks whether the node is part of the view.
* @details The node is part of the view if the supplied pointer is identical to the view definition pointer, when the view
@@ -367,95 +518,8 @@ namespace toml_parser
*/
virtual std::string GenerateTOML(const CGenContext& rContext = CGenContext()) const = 0;
protected:
/**
* @brief Compose a custom path from the node key path using a key prefix and a context.
* @param[in] rssPrefixKey The prefix to insert at as a base to the key tree.
* @param[in] rssContext The context that is used to define the relative portion of the key. To determine the relative
* portion, the context string contains the same prefix as is supplied in rssPrefixKey.
* @return Returns the custom path composed of the prefix and the relative portion of the original path.
*/
std::string GetCustomPath(const std::string& rssPrefixKey, const std::string& rssContext) const;
/**
* @brief Comment or code snippet structure.
* @details Each node has multiple code snippets used to reproduce the exact code. For example with an assignment:
* @code
*
* # This is out of scope comment before
*
* # This is comment before
* var_a = "abc" # comment behind
* # more comment behind
*
* # This is out of scope comment behind
*
* @endcode
*
* The code snippets are identified as follows:
* @code
* <out of scope comment before>
* <comment before>
* <space before><key><space>=<space><value><comment behind>
* <out of scope comment behind>
* @endcode
*/
class CCodeSnippet
{
public:
/**
* @brief Access the token list.
* @return Reference to the list with tokens.
*/
std::list<CToken>& List();
/**
* @brief Access the comment text string.
* @return Reference to the comment string.
*/
std::string& Str();
/**
* @brief Mode the code snippet composer should run in.
*/
enum class EComposeMode
{
compose_inline, ///< Compose as inline whitespace and comment. If there is no token list and no comment
///< string, compose as one space. If there is only a comment string, insert a space, add
///< the comment followed by an obligatory newline, and insert spaces until the next
///< provided position. If there are tokens with a comment token, replace the comment. If
///< there are tokens without comment, add the comment, newline and spaces.
compose_before, ///< Compose as comment assigned to and located before the node. If there is no token list
///< and no comment string, doesn't add anything. If there is only a comment string, adds
///< the comment followed by the obligatory newline. If there are tokens with a comment
///< token, replace the comment. If there are tokens without the comment, place the comment
///< before the last newline or when not available, at the end of the tokens followed by a
///< new newline.
compose_behind, ///< Compose as comment assigned to and located behind the node. If there is no token list
///< and no comment string, add a newline. If there is a comment string and no tokens,
///< add a space, the comment string followed by the obligatory newline. If there is a token
///< list without comment, add a comment before the newline or at the end with an additional
///< newline.
compose_standalone, ///< Compose as stand-alone comment. Replace any token list if a comment string is
///< available.
};
/**
* @brief Compose a conde string from the stored tokens and/or string.
* @param[in] eMode The mode the composer should run in.
* @param[in] nAssignmentOffset The offset for a next assignent; only used for inline composition.
* @param[in] nCommentOffset The offset to insert a multi-line comment; only used for inline and behind composition.
* @return The composed code string.
*/
std::string Compose(EComposeMode eMode, size_t nAssignmentOffset = 0, size_t nCommentOffset = 0) const;
private:
std::list<CToken> m_lstTokens; ///< Token list for the code snippet in raw format.
std::string m_ssComment; ///< The comment text for the code snippet in text format.
};
// White space and comment preservation indices for code generation.
const size_t m_nPreNodeCode = 0; ///< Code snippet before the node. Corresponds to
const size_t m_nPreNodeCode = 0; ///< Code snippet before the node. Corresponds to
///< sdv::toml::INodeInfo::ECommentFlags::comment_before.
const size_t m_nPostNodeCode = 1; ///< Comment behind the node. Corresponds to
///< sdv::toml::INodeInfo::ECommentFlags::comment_behind.
@@ -473,7 +537,7 @@ namespace toml_parser
/**
* @brief Get the code snippet.
* @param[in] nIndex The comment type index to get the comment for.
* @param[in] nIndex The comment type index to get the code for.
* @param[in] rssKey Reference to the key to be used for code snippet identification.
* @return Reference to the comment structure of the comment. If the provided index is not available in the vector,
* returns an empty code snippet.
@@ -481,15 +545,38 @@ namespace toml_parser
const CCodeSnippet& CodeSnippet(size_t nIndex, const std::string& rssKey = std::string()) const;
/**
* @brief Get the code snippet (write access). This allows moving the snippet from one node to the another node.
* @remarks Since the request to the code snippet could change the location of the vector allocation, access to the code
* snippet is valid until the next code snippet is requested.
* @param[in] nIndex The comment type index to get the comment for.
* @brief Get the code snippet (write access).
* @param[in] nIndex The comment type index to get the code for.
* @param[in] rssKey Reference to the key to be used for code snippet identification.
* @return Reference to the comment structure of the comment.
*/
CCodeSnippet& CodeSnippet(size_t nIndex, const std::string& rssKey = std::string());
/**
* @brief Get the code snippet using the comment type.
* @param[in] eType The comment type to get the code for.
* @return Reference to the comment structure of the comment. If the provided index is not available in the vector,
* returns an empty code snippet.
*/
const CCodeSnippet& CodeSnippet(sdv::toml::INodeInfo::ECommentType eType) const;
/**
* @brief Get the code snippet (write access) using the comment type.
* @param[in] eType The comment type to get the code for.
* @return Reference to the comment structure of the comment. If the provided index is not available in the vector,
* returns an empty code snippet.
*/
CCodeSnippet& CodeSnippet(sdv::toml::INodeInfo::ECommentType eType);
/**
* @brief Compose a custom path from the node key path using a key prefix and a context.
* @param[in] rssPrefixKey The prefix to insert at as a base to the key tree.
* @param[in] rssContext The context that is used to define the relative portion of the key. To determine the relative
* portion, the context string contains the same prefix as is supplied in rssPrefixKey.
* @return Returns the custom path composed of the prefix and the relative portion of the original path.
*/
std::string GetCustomPath(const std::string& rssPrefixKey, const std::string& rssContext) const;
private:
std::weak_ptr<CNodeCollection> m_ptrParent; ///< Weak pointer to the parent node (if existing).
std::weak_ptr<CNodeCollection> m_ptrView; ///< Weak pointer to the view node (if existing and explicitly set).
@@ -610,6 +697,12 @@ namespace toml_parser
*/
std::string RawValueText() const;
protected:
/**
* @brief When updating the node, reset the raw value text.
*/
void ResetRawValueText();
private:
std::string m_ssRawValue; ///< Raw value string.
};
@@ -647,7 +740,7 @@ namespace toml_parser
/**
* @brief Change the value of the node. Overload of sdv::toml::INodeUpdate::ChangeValue.
* @remarks Only valid for value nodes. Changing the value type is not supported.
* @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a
* @param[in] anyNewValue The value of the node, being either an integer, floating point number, virtual bool value or a
* string. Conversion is automatically done to int64, double float, bool or u8string.
* @return Returns whether the value change was successful.
*/
@@ -660,7 +753,7 @@ namespace toml_parser
virtual std::string ValueText() const override;
private:
bool m_bVal = false; ///< Value in case of boolean node.
bool m_bVal = false; ///< Value in case of virtual bool node.
};
/**
@@ -696,7 +789,7 @@ namespace toml_parser
/**
* @brief Change the value of the node. Overload of sdv::toml::INodeUpdate::ChangeValue.
* @remarks Only valid for value nodes. Changing the value type is not supported.
* @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a
* @param[in] anyNewValue The value of the node, being either an integer, floating point number, virtual bool value or a
* string. Conversion is automatically done to int64, double float, bool or u8string.
* @return Returns whether the value change was successful.
*/
@@ -745,7 +838,7 @@ namespace toml_parser
/**
* @brief Change the value of the node. Overload of sdv::toml::INodeUpdate::ChangeValue.
* @remarks Only valid for value nodes. Changing the value type is not supported.
* @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a
* @param[in] anyNewValue The value of the node, being either an integer, floating point number, virtual bool value or a
* string. Conversion is automatically done to int64, double float, bool or u8string.
* @return Returns whether the value change was successful.
*/
@@ -806,7 +899,7 @@ namespace toml_parser
/**
* @brief Change the value of the node. Overload of sdv::toml::INodeUpdate::ChangeValue.
* @remarks Only valid for value nodes. Changing the value type is not supported.
* @param[in] anyNewValue The value of the node, being either an integer, floating point number, boolean value or a
* @param[in] anyNewValue The value of the node, being either an integer, floating point number, virtual bool value or a
* string. Conversion is automatically done to int64, double float, bool or u8string.
* @return Returns whether the value change was successful.
*/
@@ -826,8 +919,11 @@ namespace toml_parser
/**
* @brief Base structure for arrays and tables.
*/
class CNodeCollection : public CNode, public sdv::toml::INodeCollection, public sdv::toml::INodeCollectionInsert
class CNodeCollection : public CNode, public sdv::toml::INodeCollection, public sdv::toml::INodeCollectionInsert,
public sdv::toml::INodeCollectionConvert
{
// Friend class CNode.
friend CNode;
protected:
/**
* @brief Constructor
@@ -842,9 +938,16 @@ namespace toml_parser
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::toml::INodeCollection)
SDV_INTERFACE_ENTRY(sdv::toml::INodeCollectionInsert)
SDV_INTERFACE_ENTRY(sdv::toml::INodeCollectionConvert)
SDV_INTERFACE_CHAIN_BASE(CNode)
END_SDV_INTERFACE_MAP()
/**
* @brief Format the node automatically. This will remove the whitespace between the elements within the node. Comments
* will not be changed. Overload of sdv::toml::INodeInfo::AutomaticFormat.
*/
virtual void AutomaticFormat() override;
/**
* @brief Returns the amount of nodes. Overload of sdv::toml::INodeCollection::GetCount.
* @return The amount of nodes.
@@ -900,7 +1003,7 @@ namespace toml_parser
* @param[in] ssName Name of the node to insert. Will be ignored for an array collection. The name must adhere to the
* key names defined by the TOML specification. Defining the key multiple times is not allowed. Quotation of key names
* is done automatically; the parser decides itself whether the key is bare-key, a literal key or a quoted key.
* @param[in] anyValue The value of the node, being either an integer, floating point number, boolean value or a string.
* @param[in] anyValue The value of the node, being either an integer, floating point number, virtual bool value or a string.
* Conversion is automatically done to int64, double float, bool or u8string.
* @return On success the interface to the newly inserted node is returned or NULL otherwise.
*/
@@ -928,13 +1031,15 @@ namespace toml_parser
* collection count to insert the node at the end of the collection. Table nodes cannot be inserted before value nodes
* or arrays. If the index is referencing a position before a value node or an array, the index is automatically
* corrected.
* @param[in] ssKeyName Name of the table node to insert. Will be ignored if the current node is an array collection.
* @param[in] ssName Name of the table node to insert. Will be ignored if the current node is an array collection.
* The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not
* allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a
* literal key or a quoted key.
* @param[in] ePreference The preferred form of the node to be inserted.
* @return On success the interface to the newly inserted node is returned or NULL otherwise.
*/
virtual sdv::IInterfaceAccess* InsertTable(uint32_t uiIndex, const sdv::u8string& ssKeyName) override;
virtual sdv::IInterfaceAccess* InsertTable(uint32_t uiIndex, const sdv::u8string& ssName,
sdv::toml::INodeCollectionInsert::EInsertPreference ePreference) override;
/**
* @brief Insert a table array into the collection at the location before the supplied index. Overload of
@@ -947,36 +1052,82 @@ namespace toml_parser
* The name must adhere to the key names defined by the TOML specification. Defining the key multiple times is not
* allowed. Quotation of key names is done automatically; the parser decides itself whether the key is bare-key, a
* literal key or a quoted key.
* @param[in] ePreference The preferred form of the node to be inserted.
* @return On success the interface to the newly inserted node is returned or NULL otherwise.
*/
virtual sdv::IInterfaceAccess* InsertTableArray(uint32_t uiIndex, const sdv::u8string& ssName) override;
virtual sdv::IInterfaceAccess* InsertTableArray(uint32_t uiIndex, const sdv::u8string& ssName,
sdv::toml::INodeCollectionInsert::EInsertPreference ePreference) override;
/**
* @brief Insert a TOML string as a child of the current collection node. If the collection is a table, the TOML string
* should contain values and inline/external/array-table nodes with names. If the collection is an array, the TOML
* string should contain and inline table nodes without names. Overload of sdv::toml::INodeCollectionInsert::InsertTOML.
* @param[in] uiIndex The insertion location to insert the node before. Can be npos or any value larger than the
* collection count to insert the node at the end of the collection. Table array nodes cannot be inserted before value
* nodes or arrays. If the index is referencing a position before a value node or an array, the index is automatically
* corrected.
* @param[in] ssTOML The TOML string to insert.
* @param[in] bRollbackOnPartly If only part of the nodes could be inserted, no node will be inserted.
* @return The result of the insertion.
*/
virtual sdv::toml::INodeCollectionInsert::EInsertResult InsertTOML(const sdv::u8string& ssTOML,
virtual sdv::toml::INodeCollectionInsert::EInsertResult InsertTOML(uint32_t uiIndex, const sdv::u8string& ssTOML,
bool bRollbackOnPartly) override;
/**
* @brief Delete the current node. Overload of sdv::toml::INodeDelete::DeleteNode.
* @brief Delete the current node. Overload of sdv::toml::INodeUpdate::DeleteNode.
* @attention A successful deletion will cause all interfaces to the current node to become inoperable.
* @return Returns whether the deletion was successful.
*/
virtual bool DeleteNode() override;
//protected:
/**
* @brief Remove a node from the collection.
* @brief Can the node convert to an inline definition? Overload of sdv::toml::INodeCollectionConvert::CanMakeInline.
* @return Returns whether the conversion to inline is possible. Returns 'true' when the node is already inline.
*/
virtual bool CanMakeInline() const override;
/**
* @brief Convert the node to an inline node. Overload of sdv::toml::INodeCollectionConvert::MakeInline.
* @return Returns whether the conversion was successful. Returns 'true' when the node was already inline.
*/
virtual bool MakeInline() override;
/**
* @brief Can the node convert to a standard definition? Overload of sdv::toml::INodeCollectionConvert::CanMakeStandard.
* @return Returns whether the conversion to standard is possible. Returns 'true' when the node is already defined as
* standard node.
*/
virtual bool CanMakeStandard() const override;
/**
* @brief Convert the node to a standard node. Overload of sdv::toml::INodeCollectionConvert::MakeStandard.
* @return Returns whether the conversion was successful. Returns 'true' when the node was already defined as standard
* node.
*/
virtual bool MakeStandard() override;
/**
* @brief Delete a node from the collection.
* @remarks The node will not be deleted, but placed in the recycle bin. Deletion will take place at collection destruction.
* @param[in] rptrNode Reference to the smart pointer pointing to the node to remove.
* @return Returns whether the removal was successful.
*/
bool RemoveNode(const std::shared_ptr<CNode>& rptrNode);
bool DeleteNode(const std::shared_ptr<CNode>& rptrNode);
/**
* @brief Insert the node into the view.
* @remarks The node must be a descendant (direct child or an indirect child) of the node.
* @details Insert the node into the vector (and remove it from any previous vector if still assigned). Inline nodes can be
* assigned to stadard and inline nodes. Standard nodes can only be assigned to standard nodes. In the vector, first the
* inline nodes and then the standard nodes are located. This means that an inline node will be placed before standard nodes
* and standard nodes behind inline nodes, regardless of the index provided.
* @param[in] uiIndex Location within the vector to insert the node. Could be sdv::toml::npos to insert as last node.
* @param[in] rptrNode Reference to the smart pointer to the node to set the view for. If the node is not a direct child
* node, the view pointer of the node will be set.
* @return Returns true when the insertion was successful, false when node (likely because the node to insert is a standard
* node, whereas this node is an inline node.
*/
bool InsertIntoView(uint32_t uiIndex, const std::shared_ptr<CNode>& rptrNode);
/**
* @brief Remove a node from a view.
@@ -985,20 +1136,19 @@ namespace toml_parser
*/
bool RemoveFromView(const std::shared_ptr<CNode>& rptrNode);
/**
* @brief Check whether this node is the last node in the collection.
* @param[in] rptrNode Reference to the smart pointer pointing to the node to check for.
* @return Returns whether the provided node is the last node in the collection.
*/
bool CheckLast(const std::shared_ptr<CNode>& rptrNode);
public:
/**
* @brief Find the index belonging to the provided node.
* @param[in] rptrNode Reference to the smart pointer holding the node to return the index for.
* @return Return the node index. Returns npos if the node could not be found.
*/
uint32_t FindIndex(const std::shared_ptr<CNode>& rptrNode);
uint32_t FindIndex(const std::shared_ptr<CNode>& rptrNode) const;
/**
* @brief Is the provided child node a direct or indirect child node?
* @param[in] rptrNode Reference to the smart pointer of the potential descendant node.
* @return Returns whether the provided node is a descendant of the this node.
*/
bool IsDescendant(const std::shared_ptr<CNode>& rptrNode) const;
/**
* @brief Generic inserting function for nodes.
@@ -1023,6 +1173,24 @@ namespace toml_parser
template <typename TNodeType, typename... TArgs>
std::shared_ptr<CNode> Insert(uint32_t uiIndex, const CTokenRange& rrangeKeyPath, const TArgs&... rtArgs);
/**
* @brief Combine the collection with the provided content (mathematical union).
* @details Update the child nodes with the child nodes of the provided collection. Extend the collection with nodes that do
* not exist and update the existing nodes with new values.
* @param[in] rptrCollection Reference to the collection being used for this operation.
* @return Returns whether the combination was successful.
*/
virtual bool Combine(const std::shared_ptr<CNodeCollection>& rptrCollection) = 0;
/**
* @brief Reduce the collection with by the provided content (mathematical difference).
* @details Reduce the child node with the child nodes already defined and identical in the provided collection. Different
* nodes or nodes that are not present in the collection remain.
* @param[in] rptrCollection Reference to the collection being used for this operation.
* @return Returns whether the reduction was successful.
*/
virtual bool Reduce(const std::shared_ptr<CNodeCollection>& rptrCollection) = 0;
private:
/**
* @brief When set, the child nodes need grouping (values following each other, tables and table arrays at the end).
@@ -1156,8 +1324,23 @@ namespace toml_parser
*/
virtual void MakeExplicit() override;
/**
* @brief Combine the collection with the provided content (mathematical union). Overload of CNodeCollection::Combine.
* @details Update the child nodes with the child nodes of the provided collection. Extend the collection with nodes that do
* not exist and update the existing nodes with new values.
* @param[in] rptrCollection Reference to the collection being used for this operation.
* @return Returns whether the combination was successful.
*/
virtual bool Combine(const std::shared_ptr<CNodeCollection>& rptrCollection) override;
//bool m_bOpenToAddChildren = true; ///< If internal table, the table can be extended until the table is closed.
/**
* @brief Reduce the collection with by the provided content (mathematical difference). Overload of CNodeCollection::Reduce.
* @details Reduce the child node with the child nodes already defined and identical in the provided collection. Different
* nodes or nodes that are not present in the collection remain.
* @param[in] rptrCollection Reference to the collection being used for this operation.
* @return Returns whether the reduction was successful.
*/
virtual bool Reduce(const std::shared_ptr<CNodeCollection>& rptrCollection) override;
private:
bool m_bDefinedExplicitly = true; ///< When set, the table is defined explicitly.
@@ -1267,6 +1450,13 @@ namespace toml_parser
*/
bool TableArray() const;
/**
* @brief Can the node convert to a standard definition? Overload of sdv::toml::INodeCollectionConvert::CanMakeStandard.
* @return Returns whether the conversion to standard is possible. Returns 'true' when the node is already defined as
* standard node.
*/
virtual bool CanMakeStandard() const override;
/**
* @brief The derived class from the node collection can be inline or not. Overload of CNode::Inline.
* @return Returns whether the node is an inline node.
@@ -1287,9 +1477,34 @@ namespace toml_parser
*/
virtual bool Inline(bool bInline) override;
/**
* @brief Does the last child node need a comma following the node?
* @return Returns whether the array was defined with the last node requiring a comma following the node.
*/
bool LastNodeWithSucceedingComma() const;
/**
* @brief Combine the collection with the provided content (mathematical union). Overload of CNodeCollection::Combine.
* @details Update the child nodes with the child nodes of the provided collection. Extend the collection with nodes that do
* not exist and update the existing nodes with new values.
* @param[in] rptrCollection Reference to the collection being used for this operation.
* @return Returns whether the combination was successful.
*/
virtual bool Combine(const std::shared_ptr<CNodeCollection>& rptrCollection) override;
/**
* @brief Reduce the collection with by the provided content (mathematical difference). Overload of CNodeCollection::Reduce.
* @details Reduce the child node with the child nodes already defined and identical in the provided collection. Different
* nodes or nodes that are not present in the collection remain.
* @param[in] rptrCollection Reference to the collection being used for this operation.
* @return Returns whether the reduction was successful.
*/
virtual bool Reduce(const std::shared_ptr<CNodeCollection>& rptrCollection) override;
private:
bool m_bDefinedExplicitly = true; ///< When set, the array is defined explicitly.
bool m_bInline = false; ///< Flag determining whether the table is inline or not.
bool m_bDefinedExplicitly = true; ///< When set, the array is defined explicitly.
bool m_bInline = false; ///< Flag determining whether the table is inline or not.
bool m_bLastChildNodeWithComma = false; ///< Set when the last child node of the array initially has a comma behind the node.
};
/**
@@ -1323,7 +1538,7 @@ namespace toml_parser
{}
/**
* @brief Delete the current node. Overload of sdv::toml::INodeDelete::DeleteNode.
* @brief Delete the current node. Overload of sdv::toml::INodeUpdate::DeleteNode.
* @attention A successful deletion will cause all interfaces to the current node to become inoperable.
* @return Returns whether the deletion was successful.
*/
@@ -1387,10 +1602,9 @@ namespace toml_parser
{
ptrTableArray = std::make_shared<CTableArray>(Parser(), prKey.first.get().StringValue(),
prKey.first.get().RawString());
ptrTableArray->SetParentPtr(Cast<CNodeCollection>());
// Add to the list
m_lstNodes.push_back(ptrTableArray);
// Set the parent pointer; this will add the node to the list.
ptrTableArray->SetParentPtr(Cast<CNodeCollection>());
}
else
ptrTableArray = (*itNode)->template Cast<CTableArray>();
@@ -1401,6 +1615,7 @@ namespace toml_parser
// Create the table.
ptrNode = ptrTableArray->Insert<CTable>(sdv::toml::npos, CTokenRange(prKey.first, prKey.first.get().Next()),
false, true);
ptrNode->SetViewPtr(Cast<CNodeCollection>());
} else if (!Cast<CArray>() && itNode != m_lstNodes.end())
{
// If existing... this might be a duplicate if not explicitly defined before.
@@ -1422,10 +1637,9 @@ namespace toml_parser
// Create the target node.
ptrNode = std::make_shared<TNodeType>(Parser(), prKey.first.get().StringValue(), prKey.first.get().RawString(),
rtArgs...);
ptrNode->SetParentPtr(Cast<CNodeCollection>());
// Add to the list
m_lstNodes.push_back(ptrNode);
// Set the parent pointer; this will add the node to the list.
ptrNode->SetParentPtr(Cast<CNodeCollection>());
// If the current node is implicit, take over the inline flag (this determines whether a sub-table definition is
// allowed or not).
@@ -1480,7 +1694,6 @@ namespace toml_parser
// Create an implicit table node.
ptrNode = std::make_shared<CTable>(Parser(), prKey.first.get().StringValue(), prKey.first.get().RawString(), false, false);
ptrNode->SetParentPtr(Cast<CNodeCollection>());
m_lstNodes.push_back(ptrNode);
}
// Insert the node in the child node
@@ -1490,15 +1703,14 @@ namespace toml_parser
ptrNode = ptrNodeCollection->Insert<TNodeType>(sdv::toml::npos, prKey.second, rtArgs...);
if (!ptrNode)
throw XTOMLParseException("Could not create the node '" + prKey.first.get().StringValue() + "'.");
// This node is not the parent, but still presents of the created node. Add the view pointer that they are linked.
ptrNode->SetViewPtr(Cast<CNodeCollection>());
}
// Insert the node at the requested location if this node is inline or this is the view of the node.
auto itPos = (static_cast<size_t>(uiIndex) >= m_vecNodeOrder.size()) ? m_vecNodeOrder.end() :
m_vecNodeOrder.begin() + static_cast<size_t>(uiIndex);
m_vecNodeOrder.insert(itPos, ptrNode);
if (ptrNode->GetParentPtr() != Cast<CNodeCollection>())
ptrNode->SetViewPtr(Cast<CNodeCollection>());
// Return the result
return ptrNode;

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#include "parser_toml.h"
#include <iostream>
#include "miscellaneous.h"
@@ -15,7 +29,7 @@ namespace toml_parser
{
m_ptrRoot.reset();
m_ptrCurrentCollection.reset();
m_lexer.Reset();
m_lexer.Clear();
while (!m_stackEnvironment.empty())
m_stackEnvironment.pop();
}
@@ -109,6 +123,13 @@ namespace toml_parser
CNodeCollection& CParser::Root()
{
// Create the root node if not existing.
if (!m_ptrRoot)
{
m_ptrRoot = std::make_shared<CRootTable>(*this);
m_ptrCurrentCollection = m_ptrRoot;
}
auto ptrCollection = m_ptrRoot->Cast<CTable>();
return *ptrCollection.get();
}
@@ -246,7 +267,7 @@ namespace toml_parser
auto ptrCurrentCollectionStored = m_ptrCurrentCollection;
ptrNode = m_ptrCurrentCollection->Insert<CArray>(sdv::toml::npos, rrangeKeyPath);
m_ptrCurrentCollection = ptrNode->Cast<CNodeCollection>();
m_stackEnvironment.push(EEnvironment::env_array);
m_stackEnvironment.push(EEnvironment::array);
ProcessArray(rNodeRange);
m_stackEnvironment.pop();
m_ptrCurrentCollection = ptrCurrentCollectionStored;
@@ -257,7 +278,7 @@ namespace toml_parser
auto ptrCurrentCollectionStored = m_ptrCurrentCollection;
ptrNode = m_ptrCurrentCollection->Insert<CTable>(sdv::toml::npos, rrangeKeyPath, true);
m_ptrCurrentCollection = ptrNode->Cast<CNodeCollection>();
m_stackEnvironment.push(EEnvironment::env_inline_table);
m_stackEnvironment.push(EEnvironment::inline_table);
ProcessInlineTable(rNodeRange);
m_stackEnvironment.pop();
m_ptrCurrentCollection = ptrCurrentCollectionStored;
@@ -275,8 +296,9 @@ namespace toml_parser
std::reference_wrapper<const CToken> refToken = m_lexer.Peek();
if (!m_stackEnvironment.empty())
{
// Skip newlines
while (refToken.get().Category() == ETokenCategory::token_syntax_new_line)
// Skip newlines if not an inline table
while (m_stackEnvironment.top() != EEnvironment::inline_table &&
refToken.get().Category() == ETokenCategory::token_syntax_new_line)
{
m_lexer.Consume();
refToken = m_lexer.Peek();
@@ -284,7 +306,7 @@ namespace toml_parser
switch (m_stackEnvironment.top())
{
case EEnvironment::env_array:
case EEnvironment::array:
{
int32_t index = 1;
while (refToken.get() && refToken.get().Category() == ETokenCategory::token_syntax_new_line)
@@ -299,7 +321,7 @@ namespace toml_parser
}
}
break;
case EEnvironment::env_inline_table:
case EEnvironment::inline_table:
if (!refToken.get()
|| (refToken.get().Category() != ETokenCategory::token_syntax_comma
&& refToken.get().Category() != ETokenCategory::token_syntax_inline_table_close))
@@ -433,7 +455,7 @@ namespace toml_parser
switch (refToken.get().Category())
{
case ETokenCategory::token_syntax_new_line:
m_lexer.Consume();
throw XTOMLParseException("Line break within an inline table is not allowed.");
break;
case ETokenCategory::token_syntax_comma:
if (eExpect != EExpect::comma_or_end) throw XTOMLParseException("Expecting value or table end.");

View File

@@ -1,3 +1,17 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Martin Stimpfl - initial API and implementation
* Erik Verhoeven - writing TOML and whitespace preservation
********************************************************************************/
#ifndef PARSER_TOML_H
#define PARSER_TOML_H
@@ -19,16 +33,11 @@ namespace toml_parser
class CParser : public sdv::IInterfaceAccess, public sdv::toml::ITOMLParser
{
public:
/**
* @brief Default constructor
*/
CParser() = default;
/**
* @brief Construct a new Parser object
* @param[in] rssString UTF-8 encoded data of a TOML source
*/
CParser(const std::string& rssString);
CParser(const std::string& rssString = std::string());
// Interface map
BEGIN_SDV_INTERFACE_MAP()
@@ -125,14 +134,15 @@ namespace toml_parser
*/
enum class EEnvironment
{
env_array, ///< Environment for an array
env_inline_table ///< Environment for a table
none, ///< No nested environment (used as default environment).
array, ///< Environment for an array
inline_table ///< Environment for a table
};
std::stack<EEnvironment> m_stackEnvironment; ///< Tracking of environments in nested structures.
std::shared_ptr<CRootTable> m_ptrRoot; ///< The one root node.
std::shared_ptr<CNodeCollection> m_ptrCurrentCollection; ///< The current collection node.
CLexer m_lexer; ///< Lexer.
enum_stack<EEnvironment, EEnvironment::none> m_stackEnvironment; ///< Tracking of environments in nested structures.
std::shared_ptr<CRootTable> m_ptrRoot; ///< The one root node.
std::shared_ptr<CNodeCollection> m_ptrCurrentCollection; ///< The current collection node.
CLexer m_lexer; ///< Lexer.
};
} // namespace toml_parser

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef CONFIG_UTILITY_H
#define CONFIG_UTILITY_H
@@ -21,13 +34,13 @@ public:
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::Utility)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::utility)
DECLARE_OBJECT_CLASS_NAME("TOMLParserUtility")
private:
toml_parser::CParser m_parser; ///< Configuration parser
};
DEFINE_SDV_OBJECT_NO_EXPORT(CTOMLParserUtility)
DEFINE_SDV_OBJECT(CTOMLParserUtility)
#endif // !defined CONFIG_UTILITY_H

View File

@@ -1,3 +1,16 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Erik Verhoeven - initial API and implementation
#*******************************************************************************
# Define project
project(sdv_service_datadispatchservice VERSION 1.0 LANGUAGES CXX)

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "dispatchservice.h"
@@ -9,7 +22,7 @@ CDispatchService::CDispatchService()
sdv::IInterfaceAccess* CDispatchService::CreateTxTrigger(uint32_t uiCycleTime, uint32_t uiDelayTime, uint32_t uiBehaviorFlags,
sdv::IInterfaceAccess* pTriggerCallback)
{
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
if (GetObjectState() != sdv::EObjectState::configuring) return nullptr;
// Check for parameter validity
if (!uiCycleTime && !(uiBehaviorFlags & static_cast<uint32_t>(sdv::core::ISignalTransmission::ETxTriggerBehavior::spontaneous)))
@@ -39,7 +52,7 @@ sdv::IInterfaceAccess* CDispatchService::CreateTxTrigger(uint32_t uiCycleTime, u
sdv::IInterfaceAccess* CDispatchService::RegisterTxSignal(/*in*/ const sdv::u8string& ssSignalName, /*in*/ sdv::any_t anyDefVal)
{
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
if (GetObjectState() != sdv::EObjectState::configuring) return nullptr;
std::unique_lock<std::mutex> lock(m_mtxSignals);
@@ -49,7 +62,7 @@ sdv::IInterfaceAccess* CDispatchService::RegisterTxSignal(/*in*/ const sdv::u8st
sdv::IInterfaceAccess* CDispatchService::RegisterRxSignal(/*in*/ const sdv::u8string& ssSignalName)
{
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
if (GetObjectState() != sdv::EObjectState::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);
@@ -58,7 +71,7 @@ sdv::IInterfaceAccess* CDispatchService::RegisterRxSignal(/*in*/ const sdv::u8st
sdv::IInterfaceAccess* CDispatchService::RequestSignalPublisher(/*in*/ const sdv::u8string& ssSignalName)
{
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
if (GetObjectState() != sdv::EObjectState::configuring) return nullptr;
std::unique_lock<std::mutex> lock(m_mtxSignals);
auto itSignal = m_mapTxSignals.find(ssSignalName);
@@ -70,7 +83,7 @@ sdv::IInterfaceAccess* CDispatchService::RequestSignalPublisher(/*in*/ const sdv
sdv::IInterfaceAccess* CDispatchService::AddSignalSubscription(/*in*/ const sdv::u8string& ssSignalName, /*in*/ IInterfaceAccess* pSubscriber)
{
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
if (GetObjectState() != sdv::EObjectState::configuring) return nullptr;
std::unique_lock<std::mutex> lock(m_mtxSignals);
auto itSignal = m_mapRxSignals.find(ssSignalName);
@@ -118,42 +131,15 @@ uint64_t CDispatchService::GetDirectTransactionID() const
return m_uiDirectTransactionID;
}
void CDispatchService::Initialize(const sdv::u8string& /*ssObjectConfig*/)
bool CDispatchService::OnInitialize()
{
m_eObjectStatus = sdv::EObjectStatus::initializing;
m_scheduler.Start();
m_eObjectStatus = sdv::EObjectStatus::initialized;
return true;
}
sdv::EObjectStatus CDispatchService::GetStatus() const
void CDispatchService::OnShutdown()
{
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)

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef DISPATCH_SERVICE_H
#define DISPATCH_SERVICE_H
@@ -28,7 +41,7 @@
* @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 sdv::core::IDispatchTransaction
{
public:
/**
@@ -41,11 +54,10 @@ public:
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_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("DataDispatchService")
DECLARE_OBJECT_SINGLETON()
DECLARE_OBJECT_DEPENDENCIES("TaskTimerService")
@@ -145,27 +157,15 @@ public:
uint64_t GetDirectTransactionID() const;
/**
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
* @param[in] ssObjectConfig Optional configuration string.
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
* @return Returns 'true' when the initialization was successful, 'false' when not.
*/
void Initialize(const sdv::u8string& ssObjectConfig) override;
virtual bool OnInitialize() override;
/**
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
* @return Return the current status of the object.
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
*/
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;
virtual void OnShutdown() override;
/**
* @brief Unregister a previously registered signal. This will render all subscriptions and provider connections invalid.
@@ -205,7 +205,6 @@ 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.

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "dispatchservice.h"
#include "signal.h"
#include "trigger.h"
@@ -159,7 +172,7 @@ void CSignal::RemoveConsumer(CConsumer* pConsumer)
void CSignal::WriteFromProvider(const sdv::any_t& ranyVal, uint64_t uiTransactionID, std::set<CTrigger*>& rsetTriggers)
{
if (m_rDispatchSvc.GetStatus() != sdv::EObjectStatus::running) return;
if (m_rDispatchSvc.GetObjectState() != sdv::EObjectState::running) return;
uint64_t uiTransactionIDTemp = uiTransactionID;
if (!uiTransactionIDTemp) uiTransactionIDTemp = m_rDispatchSvc.GetDirectTransactionID();
@@ -221,7 +234,7 @@ sdv::any_t CSignal::ReadFromConsumer(uint64_t uiTransactionID) const
void CSignal::DistributeToConsumers(const sdv::any_t& ranyVal)
{
if (m_rDispatchSvc.GetStatus() != sdv::EObjectStatus::running) return;
if (m_rDispatchSvc.GetObjectState() != sdv::EObjectState::running) return;
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
for (auto& rvtConsumer : m_mapConsumers)

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef SIGNAL_H
#define SIGNAL_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "dispatchservice.h"
#include "transaction.h"
#include "trigger.h"

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef TRANSACTION_H
#define TRANSACTION_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "dispatchservice.h"
#include "trigger.h"
#include "signal.h"
@@ -157,7 +170,7 @@ void CTrigger::RemoveSignal(/*in*/ const sdv::u8string& ssSignalName)
void CTrigger::Execute(EExecutionFlag eExecFlag /*= EExecutionFlag::spontaneous*/)
{
if (m_rDispatchSvc.GetStatus() != sdv::EObjectStatus::running) return;
if (m_rDispatchSvc.GetObjectState() != sdv::EObjectState::running) return;
// Check for allowed execution
if (eExecFlag == EExecutionFlag::spontaneous &&

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef TRIGGER_H
#define TRIGGER_H

View File

@@ -1,3 +1,16 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Erik Verhoeven - initial API and implementation
#*******************************************************************************
# Define project
project(hardware_ident VERSION 1.0 LANGUAGES CXX)

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "hardware_ident.h"
#ifdef _WIN32

View File

@@ -1,13 +1,15 @@
/**
* @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
*
*/
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef HARDWARE_IDENT_H
#define HARDWARE_IDENT_H
@@ -27,7 +29,7 @@ public:
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("HardwareIdentificationService")
DECLARE_OBJECT_SINGLETON()

View File

@@ -1,3 +1,16 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Erik Verhoeven - initial API and implementation
#*******************************************************************************
# Include cross-compilation toolchain file
include(../../cross-compile-tools.cmake)

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "com_channel.h"
#include "com_ctrl.h"
#include "marshall_object.h"

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef COM_CHANNEL_H
#define COM_CHANNEL_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "com_ctrl.h"
#include <interfaces/ipc.h>
#include <support/toml.h>
@@ -14,36 +27,13 @@ CCommunicationControl::CCommunicationControl()
CCommunicationControl::~CCommunicationControl()
{}
void CCommunicationControl::Initialize(const sdv::u8string& /*ssObjectConfig*/)
bool CCommunicationControl::OnInitialize()
{
m_eObjectStatus = sdv::EObjectStatus::initialized;
return true;
}
sdv::EObjectStatus CCommunicationControl::GetStatus() const
void CCommunicationControl::OnShutdown()
{
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)
@@ -64,8 +54,6 @@ void CCommunicationControl::Shutdown()
lock.unlock();
vecChannels.clear();
mapStubObjects.clear();
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
}
sdv::com::TConnectionID CCommunicationControl::CreateServerConnection(/*in*/ sdv::com::EChannelType eChannelType,

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef COM_CTRL_H
#define COM_CTRL_H
@@ -12,7 +25,7 @@ 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,
class CCommunicationControl : public sdv::CSdvObject, public sdv::com::IConnectionControl,
public sdv::ps::IMarshallAccess
{
public:
@@ -30,36 +43,23 @@ public:
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_CLASS_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_SINGLETON()
DECLARE_OBJECT_CLASS_NAME("CommunicationControl")
/**
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
* @param[in] ssObjectConfig Optional configuration string.
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
* @return Returns 'true' when the initialization was successful, 'false' when not.
*/
virtual void Initialize(const sdv::u8string& ssObjectConfig) override;
virtual bool OnInitialize() override;
/**
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
* @return Return the current status of the object.
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
*/
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;
virtual void OnShutdown() override;
/**
* @brief Create an IPC channel endpoint and use it for SDV communication. Overload of
@@ -194,7 +194,6 @@ public:
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.

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "marshall_object.h"
#include "com_ctrl.h"
#include <support/serdes.h>

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef MARSHALL_OBJECT_H
#define MARSHALL_OBJECT_H

View File

@@ -1,3 +1,16 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Erik Verhoeven - initial API and implementation
#*******************************************************************************
# Include cross-compilation toolchain file
include(../../cross-compile-tools.cmake)

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "client.h"
#include <support/toml.h>
#include <interfaces/com.h>
@@ -16,37 +29,13 @@ void CRepositoryProxy::DestroyObject()
m_rClient.Disconnect(m_tConnection);
}
void CClient::Initialize(const sdv::u8string& /*ssObjectConfig*/)
bool CClient::OnInitialize()
{
m_eObjectStatus = sdv::EObjectStatus::initialized;
return true;
}
sdv::EObjectStatus CClient::GetStatus() const
void CClient::OnShutdown()
{
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!");
@@ -60,8 +49,6 @@ void CClient::Shutdown()
for (const auto& rvtRepository : mapRepositoryProxiesCopy)
pConnectionControl->RemoveConnection(rvtRepository.first);
}
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
}
sdv::IInterfaceAccess* CClient::Connect(const sdv::u8string& ssConnectString)
@@ -126,14 +113,14 @@ Port = ")code" + std::to_string(uiPort) + R"code(
else
{
SDV_LOG_ERROR("Invalid or missing listener configuration for listener service!");
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
SetObjectIntoConfigErrorState();
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;
SetObjectIntoConfigErrorState();
return nullptr;
}

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef CLIENT_H
#define CLIENT_H
@@ -55,42 +68,29 @@ private:
/**
* @brief Client object
*/
class CClient : public sdv::CSdvObject, public sdv::IObjectControl, public sdv::com::IClientConnect
class CClient : public sdv::CSdvObject, 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_TYPE(sdv::EObjectType::system_object)
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.
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
* @return Returns 'true' when the initialization was successful, 'false' when not.
*/
void SetOperationMode(sdv::EOperationMode eMode) override;
virtual bool OnInitialize() override;
/**
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
*/
void Shutdown() override;
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
*/
virtual void OnShutdown() override;
/**
* @brief Connect to a remote system using the connection string to contact the system. Overload of
@@ -122,7 +122,6 @@ public:
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.
};

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "listener.h"
#include <support/toml.h>
#include <interfaces/com.h>
@@ -65,84 +78,63 @@ sdv::u8string CChannelBroker::RequestChannel(/*in*/ const sdv::u8string& /*ssCon
CListener::CListener() : m_broker(*this)
{}
void CListener::Initialize(const sdv::u8string& ssObjectConfig)
bool CListener::OnInitialize()
{
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;
return false;
}
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;
return false;
}
sdv::ipc::ICreateEndpoint* pEndpoint = nullptr;
std::string ssConfig;
try
if (m_ssType == "Local")
{
// 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")
m_bLocalListener = true;
pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>("LocalChannelControl");
if (!pEndpoint)
{
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;
}
SDV_LOG_ERROR("No local channel control!");
return false;
}
// Request the instance ID from the app control
ssConfig = std::string(R"code([IpcChannel]
Name = "LISTENER_)code") + std::to_string(uiInstanceID ? uiInstanceID : pContext->GetInstanceID()) + R"code("
// Request the instance ID from the app control
ssConfig = std::string(R"code([IpcChannel]
Name = "LISTENER_)code") + std::to_string(m_uiInstanceID ? m_uiInstanceID : pContext->GetInstanceID()) + R"code("
Size = 2048
)code";
}
else if (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)
else if (m_ssType == "Remote")
{
SDV_LOG_ERROR("Invalid service configuration for listener service: ", rexcept.what(), "!");
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
return;
m_bLocalListener = false;
if (m_ssInterface.empty() || !m_uiPort)
{
SDV_LOG_ERROR("Missing interface or port number to initialize a remote listener!");
return false;
}
pEndpoint = sdv::core::GetObject<sdv::ipc::ICreateEndpoint>("RemoteChannelControl");
if (!pEndpoint)
{
SDV_LOG_ERROR("No remote channel control!");
return false;
}
ssConfig = R"code([IpcChannel]
Interface = ")code" + m_ssInterface + R"code(
Port = ")code" + std::to_string(m_uiPort) + R"code(
)code";
}
else
{
SDV_LOG_ERROR("Invalid or missing listener configuration for listener service!");
return false;
}
// Create the endpoint
@@ -150,8 +142,7 @@ Port = ")code" + std::to_string(uiPort) + R"code(
if (!sEndpoint.pConnection)
{
SDV_LOG_ERROR("Could not create the endpoint for listener service!");
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
return;
return false;
}
sdv::TObjectPtr ptrEndpoint(sEndpoint.pConnection); // Does automatic destruction if failure happens.
@@ -161,39 +152,13 @@ Port = ")code" + std::to_string(uiPort) + R"code(
if (!m_tConnection)
{
SDV_LOG_ERROR("Could not assign the server endpoint!");
m_eObjectStatus = sdv::EObjectStatus::initialization_failure;
return;
return false;
}
m_eObjectStatus = sdv::EObjectStatus::initialized;
return true;
}
sdv::EObjectStatus CListener::GetStatus() const
void CListener::OnShutdown()
{
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{})
{
@@ -206,8 +171,6 @@ void CListener::Shutdown()
}
m_ptrConnection.Clear();
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
}
bool CListener::IsLocalListener() const

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef LISTENER_H
#define LISTENER_H
@@ -40,7 +53,7 @@ private:
/**
* @brief Listener object
*/
class CListener : public sdv::CSdvObject, public sdv::IObjectControl
class CListener : public sdv::CSdvObject
{
public:
/**
@@ -48,17 +61,22 @@ public:
*/
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_TYPE(sdv::EObjectType::system_object)
DECLARE_OBJECT_CLASS_NAME("ConnectionListenerService")
// Parameter map
BEGIN_SDV_PARAM_MAP()
SDV_PARAM_ENABLE_LOCKING()
SDV_PARAM_GROUP("Listener")
SDV_PARAM_ENTRY(m_ssType, "Type", "Local", "", "The type of listener \"Local\" or \"Remote\".")
SDV_PARAM_ENTRY(m_uiInstanceID, "Instance", 0, "", "The instance ID to listen for.")
SDV_PARAM_ENTRY(m_ssInterface, "Interface", "", "", "Interface identification.")
SDV_PARAM_ENTRY(m_uiPort, "Port", 0, "", "Port number for connection.")
END_SDV_PARAM_MAP()
/**
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
* @brief Initialization event, called after object configuration was loaded. Overload of sdv::CSdvObject::OnInitialize.
* @details The object configuration contains the information needed to start the listener. The following configuration is
* available for the local listener:
* @code
@@ -73,26 +91,14 @@ public:
* Interface = "127.0.0.1"
* Port = 2000
* @endcode
* @param[in] ssObjectConfig Optional configuration string.
* @return Returns 'true' when the initialization was successful, 'false' when not.
*/
void Initialize(const sdv::u8string& ssObjectConfig) override;
virtual bool OnInitialize() override;
/**
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
* @return Return the current status of the object.
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
*/
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;
virtual void OnShutdown() override;
/**
* @brief When set, the listener is configured to be a local listener. Otherwise the listerner is configured as remote listener.
@@ -101,7 +107,10 @@ public:
bool IsLocalListener() const;
private:
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< To update the object status when it changes.
sdv::u8string m_ssType; ///< Listener type: "Local" or "Remote"
uint32_t m_uiInstanceID = 0; ///< Instance ID to listen for.
std::string m_ssInterface; ///< Interface string for remote listener.
uint32_t m_uiPort = 0; ///< Port for remote listener.
sdv::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.

View File

@@ -1,3 +1,16 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Erik Verhoeven - initial API and implementation
#*******************************************************************************
# Define project
project(ipc_shared_mem VERSION 1.0 LANGUAGES CXX)

View File

@@ -1,45 +1,23 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "channel_mgnt.h"
#include "connection.h"
#include <support/toml.h>
void CSharedMemChannelMgnt::Initialize(const sdv::u8string& /*ssObjectConfig*/)
void CSharedMemChannelMgnt::OnShutdown()
{
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)

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef CHANNEL_MGNT_H
#define CHANNEL_MGNT_H
@@ -9,8 +22,8 @@
#include "watchdog.h"
#include "connection.h"
#define TEST_DECLARE_OBJECT_CLASS_ALIAS(...) \
static sdv::sequence<sdv::u8string> GetClassAliasesStaticMyTest() \
#define TEST_DECLARE_OBJECT_CLASS_ALIAS(...) \
static sdv::sequence<sdv::u8string> GetClassAliasesStaticMyTest() \
{ \
return sdv::sequence<sdv::u8string>({__VA_ARGS__}); \
}
@@ -19,46 +32,26 @@
/**
* @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
class CSharedMemChannelMgnt : public sdv::CSdvObject, 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_TYPE(sdv::EObjectType::system_object)
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.
* @brief Shutdown the object. Overload of sdv::CSdvObject::OnShutdown.
*/
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;
virtual void OnShutdown() override;
/**
* @brief Create IPC connection object and return the endpoint information. Overload of
@@ -111,7 +104,6 @@ private:
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.
};

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "connection.h"
#include "watchdog.h"
#include <numeric>

View File

@@ -1,13 +1,15 @@
/**
* @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 (c) 2025-2026 ZF Friedrichshafen AG
*
* @copyright Copyright ZF Friedrichshafen AG (c) 2023-2025
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
*/
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef CHANNEL_H
#define CHANNEL_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef IN_PROCESS_MEM_BUFFER_H
#define IN_PROCESS_MEM_BUFFER_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "mem_buffer_accessor.h"
#include <cassert>

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef MEM_BUFFER_ACCESSOR_H
#define MEM_BUFFER_ACCESSOR_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#if !defined POSIX_SHARED_MEM_BUFFER_H && defined __unix__
#define POSIX_SHARED_MEM_BUFFER_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#if !defined WINDOWS_SHARED_MEM_BUFFER_H && defined _WIN32
#define WINDOWS_SHARED_MEM_BUFFER_H

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "watchdog.h"
#include "connection.h"
#include <vector>

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef WATCH_DOG_H
#define WATCH_DOG_H

View File

@@ -1,3 +1,16 @@
#*******************************************************************************
# Copyright (c) 2025-2026 ZF Friedrichshafen AG
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
#
# Contributors:
# Erik Verhoeven - initial API and implementation
#*******************************************************************************
# Define project
project(manifest_util VERSION 1.0 LANGUAGES CXX)

View File

@@ -1,3 +1,16 @@
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#include "manifest_util.h"
#include <string>
#include <filesystem>

View File

@@ -1,13 +1,15 @@
/**
* @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
*
*/
/********************************************************************************
* Copyright (c) 2025-2026 ZF Friedrichshafen AG
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Erik Verhoeven - initial API and implementation
********************************************************************************/
#ifndef MANIFEST_UTIL_H
#define MANIFEST_UTIL_H
@@ -31,7 +33,7 @@ public:
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::Utility)
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::utility)
DECLARE_OBJECT_CLASS_NAME("ManifestHelperUtility")
/**

Some files were not shown because too many files have changed in this diff Show More