Precommit (#1)

* first commit

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

View File

@@ -0,0 +1,72 @@
# Define project
project(sdv_services_core VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(core_services SHARED
"memory.cpp"
"memory.h"
"sdv_core.h"
"sdv_core.cpp"
"module_control.h"
"module_control.cpp"
"app_control.h"
"app_control.cpp"
"repository.h"
"repository.cpp"
"toml_parser/character_reader_utf_8.h"
"toml_parser/lexer_toml.h"
"toml_parser/parser_toml.h"
"toml_parser/parser_toml.cpp"
"toml_parser/lexer_toml.cpp"
"toml_parser/character_reader_utf_8.cpp"
"toml_parser/parser_node_toml.h"
"toml_parser/parser_node_toml.cpp"
"toml_parser/exception.h"
"module.cpp"
"app_config.h"
"app_config.cpp"
"logger_control.h"
"logger_control.cpp"
"logger.h"
"logger.cpp"
"log_csv_writer.h"
"log_csv_writer.cpp"
"object_lifetime_control.h"
"object_lifetime_control.cpp"
"toml_parser_util.h"
"installation_manifest.h"
"installation_manifest.cpp"
"local_shutdown_request.h"
"iso_monitor.h"
"iso_monitor.cpp"
"installation_composer.h"
"installation_composer.cpp"
)
# Link target
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_link_options(core_services PRIVATE)
target_link_libraries(core_services ${CMAKE_THREAD_LIBS_INIT} stdc++fs)
if (WIN32)
target_link_libraries(core_services Ws2_32 Winmm Rpcrt4.lib)
else()
target_link_libraries(core_services ${CMAKE_DL_LIBS} rt)
endif()
else()
target_link_libraries(core_services Rpcrt4.lib)
endif()
set_target_properties(core_services PROPERTIES PREFIX "")
set_target_properties(core_services PROPERTIES SUFFIX ".sdv")
# Build dependencies
add_dependencies(core_services CompileCoreIDL)
add_dependencies(core_services hardware_ident)
add_dependencies(core_services ipc_shared_mem)
add_dependencies(core_services process_control)
add_dependencies(core_services core_ps)
add_dependencies(core_services ipc_com)
# Appending the service in the service list
set(SDV_Service_List ${SDV_Service_List} core_services PARENT_SCOPE)

View File

@@ -0,0 +1,739 @@
#include "app_config.h"
#include "sdv_core.h"
#include "../../global/exec_dir_helper.h"
#include <interfaces/com.h>
#include <interfaces/process.h>
#include <interfaces/ipc.h>
#include <support/crc.h>
#if __unix__
#include <utime.h>
#endif
bool CAppConfig::LoadInstallationManifests()
{
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication()) return false;
std::filesystem::path pathCore = GetCoreDirectory();
std::filesystem::path pathExe = GetExecDirectory();
std::filesystem::path pathInstall = GetAppControl().GetInstallDir();
// Load the core manifest (should work in any case).
m_manifestCore = CInstallManifest();
if (!m_manifestCore.Load(pathCore)) return false;
// Check whether the exe path is identical to the core. If so, skip the manifest. If there is no manifest, this is not an error.
if (pathCore != pathExe)
{
m_manifestExe = CInstallManifest();
m_manifestExe.Load(pathExe);
}
else
m_manifestExe.Clear();
// Get the user manifests. If there is no manifest, this is not an error.
for (auto const& dir_entry : std::filesystem::directory_iterator{ pathInstall })
{
if (!dir_entry.is_directory()) continue;
if (!std::filesystem::exists(dir_entry.path() / "install_manifest.toml")) continue;
CInstallManifest manifestUser;
if (manifestUser.Load(dir_entry.path(), true))
m_vecUserManifests.push_back(manifestUser);
}
return true;
}
void CAppConfig::UnloadInstallatonManifests()
{
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication()) return;
m_vecUserManifests.clear();
m_manifestExe.Clear();
m_manifestCore.Clear();
}
sdv::core::EConfigProcessResult CAppConfig::ProcessConfig(/*in*/ const sdv::u8string& ssContent)
{
if (GetAppControl().GetOperationState() != sdv::app::EAppOperationState::configuring)
return sdv::core::EConfigProcessResult::failed;
// Reset the current baseline
ResetConfigBaseline();
CParserTOML parser(ssContent);
// Check for config file compatibility
auto ptrConfigVersion = parser.GetRoot().GetDirect("Configuration.Version");
if (!ptrConfigVersion)
{
SDV_LOG_ERROR("Configuration version must be present in the configuration file.");
return sdv::core::EConfigProcessResult::failed;
}
if (ptrConfigVersion->GetValue() != SDVFrameworkInterfaceVersion)
{
SDV_LOG_ERROR("Incompatible configuration file version.");
return sdv::core::EConfigProcessResult::failed;
}
// If this is not a main, isolated or maintenance application, load all modules in the component and module sections.
std::map<std::filesystem::path, sdv::core::TModuleID> mapModules;
size_t nLoadable = 0;
size_t nNotLoadable = 0;
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication() &&
!GetAppControl().IsMaintenanceApplication())
{
// Load all modules in the component section
auto ptrComponents = parser.GetRoot().GetDirect("Component");
if (ptrComponents && ptrComponents->GetArray())
{
for (uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++)
{
auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex);
if (ptrComponent)
{
// Get the information from the component.
std::filesystem::path pathModule;
auto ptrModule = ptrComponent->GetDirect("Path");
if (ptrModule) pathModule = static_cast<std::string>(ptrModule->GetValue());
// If there is no path, this is allowed... skip this module
if (pathModule.empty()) continue;
// Load the module.
sdv::core::TModuleID tModuleID = GetModuleControl().Load(pathModule.generic_u8string());
if (tModuleID)
{
mapModules[pathModule] = tModuleID;
nLoadable++;
}
else
{
SDV_LOG_ERROR("Failed to load module: ", pathModule);
nNotLoadable++;
continue;
}
}
}
}
// Load all modules from the module section.
auto ptrModules = parser.GetRoot().GetDirect("Module");
if (ptrModules && ptrModules->GetArray())
{
for (uint32_t uiIndex = 0; uiIndex < ptrModules->GetArray()->GetCount(); uiIndex++)
{
auto ptrModule = ptrModules->GetArray()->Get(uiIndex);
if (ptrModule)
{
// Get the information from the component.
std::filesystem::path pathModule;
auto ptrModulePath = ptrModule->GetDirect("Path");
if (ptrModulePath) pathModule = static_cast<std::string>(ptrModulePath->GetValue());
if (pathModule.empty())
{
SDV_LOG_ERROR("Missing module path for component configuration.");
nNotLoadable++;
continue;
}
// Load the module.
sdv::core::TModuleID tModuleID = GetModuleControl().Load(pathModule.generic_u8string());
if (tModuleID)
{
mapModules[pathModule] = tModuleID;
nLoadable++;
}
else
{
SDV_LOG_ERROR("Failed to load module: ", pathModule);
nNotLoadable++;
continue;
}
}
}
}
}
// Start all components from the component section
std::filesystem::path pathLastModule;
auto ptrComponents = parser.GetRoot().GetDirect("Component");
if (ptrComponents && ptrComponents->GetArray())
{
for (uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++)
{
auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex);
if (ptrComponent)
{
// Get the information from the component.
std::filesystem::path pathModule;
auto ptrModule = ptrComponent->GetDirect("Path");
if (ptrModule) pathModule = static_cast<std::string>(ptrModule->GetValue());
std::string ssClass;
auto ptrClass = ptrComponent->GetDirect("Class");
if (ptrClass) ssClass = static_cast<std::string>(ptrClass->GetValue());
std::string ssName;
auto ptrName = ptrComponent->GetDirect("Name");
if (ptrName) ssName = static_cast<std::string>(ptrName->GetValue());
// If there is a path, store it. If there is none, take the last stored
if (!pathModule.empty())
pathLastModule = pathModule;
else
pathModule = pathLastModule;
if (pathModule.empty())
{
SDV_LOG_ERROR("Missing module path for component configuration.");
nNotLoadable++;
continue;
}
if (ssClass.empty())
{
SDV_LOG_ERROR("Missing component class name in the configuration.");
nNotLoadable++;
continue;
}
// In case of a main, isolated or maintenance application, ignore the module. In all other cases, the module was
// loaded; get the module ID.
sdv::core::TObjectID tObjectID = 0;
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication() &&
!GetAppControl().IsMaintenanceApplication())
{
auto itModule = mapModules.find(pathModule);
if (itModule == mapModules.end()) continue; // Module was not loaded before...
tObjectID = GetRepository().CreateObjectFromModule(itModule->second, ssClass, ssName, ptrComponent->CreateTOMLText());
}
else
tObjectID = GetRepository().CreateObject2(ssClass, ssName, ptrComponent->CreateTOMLText());
if (!tObjectID)
{
SDV_LOG_ERROR("Failed to load component: ", ssClass);
nNotLoadable++;
continue;
}
else
nLoadable++;
}
}
}
if (!nNotLoadable)
return sdv::core::EConfigProcessResult::successful;
if (!nLoadable)
return sdv::core::EConfigProcessResult::failed;
return sdv::core::EConfigProcessResult::partially_successful;
}
sdv::core::EConfigProcessResult CAppConfig::LoadConfig(/*in*/ const sdv::u8string& ssConfigPath)
{
if (GetAppControl().GetOperationState() != sdv::app::EAppOperationState::configuring)
return sdv::core::EConfigProcessResult::failed;
if (ssConfigPath.empty())
return sdv::core::EConfigProcessResult::failed;
std::filesystem::path pathConfig;
if (GetAppControl().IsMainApplication())
{
pathConfig = GetAppControl().GetInstallDir() / static_cast<std::string>(ssConfigPath);
if (!std::filesystem::exists(pathConfig) || !std::filesystem::is_regular_file(pathConfig))
pathConfig.clear();
}
else
{
AddCurrentPath();
try
{
// Get the search paths if the module path is relative
std::list<std::filesystem::path> lstSearchPaths;
std::filesystem::path pathSupplied(static_cast<std::string>(ssConfigPath));
if (pathSupplied.is_relative())
lstSearchPaths = m_lstSearchPaths;
// Add an empty path to allow the OS to search when our own search paths could not find the module.
lstSearchPaths.push_back(std::filesystem::path());
// Run through the search paths and try to find the config file.
for (const std::filesystem::path& rpathDirectory : lstSearchPaths)
{
// Compose the path
std::filesystem::path pathConfigTemp;
if (rpathDirectory.is_absolute())
pathConfigTemp = (rpathDirectory / pathSupplied).lexically_normal();
else
{
if (rpathDirectory.empty())
pathConfigTemp = pathSupplied.lexically_normal();
else
pathConfigTemp = (GetExecDirectory() / rpathDirectory / pathSupplied).lexically_normal();
}
if (std::filesystem::exists(pathConfigTemp))
{
pathConfig = pathConfigTemp;
m_pathLastSearchDir = rpathDirectory;
break;
}
}
}
catch (const std::exception& re)
{
SDV_LOG_ERROR("Supplied path to config load is not valid \"", ssConfigPath, "\": ", re.what());
return sdv::core::EConfigProcessResult::failed;
}
}
if (pathConfig.empty())
{
SDV_LOG_ERROR("Configuration file was not found \"", ssConfigPath, "\"");
return sdv::core::EConfigProcessResult::failed;
}
std::ifstream fstream(pathConfig);
if (!fstream.is_open()) return sdv::core::EConfigProcessResult::failed;
std::string ssContent((std::istreambuf_iterator<char>(fstream)), std::istreambuf_iterator<char>());
return ProcessConfig(ssContent);
}
bool CAppConfig::SaveConfig(/*in*/ const sdv::u8string& ssConfigPath) const
{
std::filesystem::path pathConfig = static_cast<std::string>(ssConfigPath);
if (pathConfig.is_relative())
{
if (GetAppControl().IsMainApplication())
pathConfig = GetAppControl().GetInstallDir() / pathConfig;
else
{
if (m_pathLastSearchDir.empty())
pathConfig = GetExecDirectory() / pathConfig;
else
pathConfig = m_pathLastSearchDir / pathConfig;
}
}
std::ofstream fstream(pathConfig, std::ios::out | std::ios::trunc);
if (!fstream.is_open())
{
SDV_LOG_ERROR("Cannot open config file \"", ssConfigPath, "\"");
return false;
}
// Write header
fstream << "[Configuration]" << std::endl;
fstream << "Version = " << SDVFrameworkInterfaceVersion << " # Configuration file version." << std::endl;
// Write all objects and modules
fstream << GetRepository().SaveConfig();
fstream.close();
return true;
}
bool CAppConfig::AddConfigSearchDir(/*in*/ const sdv::u8string& ssDir)
{
if (GetAppControl().GetOperationState() != sdv::app::EAppOperationState::configuring) return false;
// Add initial paths if not done so already
AddCurrentPath();
std::unique_lock<std::mutex> lock(m_mtxSearchPaths);
std::filesystem::path pathDir(ssDir.c_str());
// Relative paths are always relative of the executable
if (pathDir.is_relative())
pathDir = GetExecDirectory() / ssDir.c_str();
// Remove any indirections
pathDir = pathDir.lexically_normal();
// If the current path is not a directory, it cannot be added
if (!std::filesystem::is_directory(pathDir)) return false;
// Check whether the path is already in the list
if (std::find(m_lstSearchPaths.begin(), m_lstSearchPaths.end(), pathDir) != m_lstSearchPaths.end())
return true; // This is not an error
// Add the path
m_lstSearchPaths.push_back(pathDir);
return true;
}
void CAppConfig::ResetConfigBaseline()
{
GetRepository().ResetConfigBaseline();
}
#ifdef _MSC_VER
// Prevent bogus warning about uninitialized memory for the variable *hFile.
#pragma warning(push)
#pragma warning(disable : 6001)
#endif
bool CAppConfig::Install(/*in*/ const sdv::u8string& ssInstallName, /*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles)
{
// Installations can only be done in the main application.
if (!GetAppControl().IsMainApplication()) return false;
std::unique_lock<std::mutex> lock(m_mtxInstallations);
// Check whether the installation already exists. If so, cancel the installation.
if (m_mapInstallations.find(ssInstallName) != m_mapInstallations.end())
{
throw sdv::installation::XDuplicateInstall();
}
// Rollback class - automatically reverses the loading.
class CRollback
{
public:
~CRollback()
{
bool bSuccess = true;
for (sdv::core::TModuleID tModuleID : m_vecLoadedModules)
{
try
{
bSuccess &= GetModuleControl().ContextUnload(tModuleID, true);
}
catch (const sdv::XSysExcept&)
{
}
catch (const std::exception&)
{
}
}
if (!bSuccess)
{
SDV_LOG_ERROR("Failed to rollback \"", m_pathInstallRoot, "\".");
return;
}
if (!m_pathInstallRoot.empty())
{
try
{
std::filesystem::remove_all(m_pathInstallRoot);
}
catch (const std::filesystem::filesystem_error& rexcept)
{
SDV_LOG_ERROR("Failed to rollback \"", m_pathInstallRoot, "\": ", rexcept.what());
}
}
}
void Commit()
{
// Do not roll back.
m_pathInstallRoot.clear();
m_vecLoadedModules.clear();
}
void SetInstallPath(const std::filesystem::path& rPathInstallDir) { m_pathInstallRoot = rPathInstallDir; }
void AddLoadedModule(sdv::core::TModuleID tModuleID) { m_vecLoadedModules.push_back(tModuleID); }
private:
std::filesystem::path m_pathInstallRoot; ///< Installation root directory
std::vector<sdv::core::TModuleID> m_vecLoadedModules; ///< loaded modules
} rollback;
std::filesystem::path pathInstallation = GetAppControl().GetInstallDir() / static_cast<std::string>(ssInstallName);
std::vector<std::filesystem::path> vecComponents;
try
{
// A sub-directory with the name must not already exist, if so, delete it.
if (std::filesystem::exists(pathInstallation))
std::filesystem::remove_all(pathInstallation);
// Create a directory for the installation
std::filesystem::create_directories(pathInstallation);
rollback.SetInstallPath(pathInstallation);
// Add each file
for (const sdv::installation::SFileDesc& rsFileDesc : seqFiles)
{
// File path
std::filesystem::path pathFileDir = pathInstallation;
//if (!rsFileDesc.ssRelativeDir.empty())
//{
// std::filesystem::path pathTemp = static_cast<std::string>(rsFileDesc.ssRelativeDir);
// if (pathTemp.is_absolute() || !pathTemp.is_relative())
// throw sdv::installation::XIncorrectPath();
// pathFileDir /= pathTemp;
// if (!std::filesystem::exists(pathFileDir))
// std::filesystem::create_directories(pathFileDir);
//}
std::filesystem::path pathFile = pathFileDir;
if (std::filesystem::exists(pathFile))
throw sdv::installation::XIncorrectPath();
// TODO EVE
//// Check the file CRC
//sdv::crcCRC32C crc;
//uint32_t uiCRC = crc.calc_checksum(rsFileDesc.ptrContent.get(), rsFileDesc.ptrContent.size());
//if (uiCRC != rsFileDesc.uiCRC32c)
// throw sdv::installation::XIncorrectCRC();
// Create the file
std::ofstream fstream(pathFile, std::ios_base::out | std::ios_base::binary);
if (!fstream.is_open()) throw sdv::installation::XIncorrectPath();
if (rsFileDesc.ptrContent.size())
fstream.write(reinterpret_cast<const char*>(rsFileDesc.ptrContent.get()), rsFileDesc.ptrContent.size());
fstream.close();
// Set file times
// Note: use dedicated OS functions. C++11 filesystem library doesn't define a proper way to convert between UNIX time
// and the time defined by the C++11 functions. This deficit has been solved in C++20.
#ifdef _WIN32
// Windows uses file access for this.
HANDLE hFile = CreateFile(pathFile.native().c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == nullptr || hFile == INVALID_HANDLE_VALUE)
{
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\"");
throw sdv::installation::XIncorrectPath();
}
// Set the filetime on the file.
// The filetime starts counting from 1st of January 1601. The resolution is 100ns.
// The UNIX time starts counting from 1st of January 1970. The resolution is 1s.
// Both times ignore leap seconds.
// Thus the UNIX time is running 11644473600 seconds behind the filetime and is a factor 10000000 larger.
ULARGE_INTEGER sCreateTime{}, sChangeTime{};
sCreateTime.QuadPart = (rsFileDesc.uiCreationDate + 11644473600ull) * 10000000ull;
FILETIME ftCreateTime{ sCreateTime.LowPart, sChangeTime.HighPart };
sChangeTime.QuadPart = (rsFileDesc.uiChangeDate + 11644473600ull) * 10000000ull;
FILETIME ftChangeTime{ sChangeTime.LowPart, sChangeTime.HighPart };
BOOL bRes = SetFileTime(hFile, &ftCreateTime, nullptr, &ftChangeTime);
// Close our handle.
CloseHandle(hFile);
if (!bRes)
{
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\"");
throw sdv::installation::XIncorrectPath();
}
#elif defined __unix__
// Note: POSIX doesn't define the creation time.
utimbuf sTimes{};
sTimes.actime = std::time(0);
sTimes.modtime = static_cast<time_t>(rsFileDesc.uiChangeDate);
if (utime(pathFile.native().c_str(), &sTimes))
{
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\"");
throw sdv::installation::XIncorrectPath();
}
#endif
// Set attributes
if (rsFileDesc.bAttrExecutable)
std::filesystem::permissions(pathFile, std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec |
std::filesystem::perms::others_exec, std::filesystem::perm_options::add);
if (rsFileDesc.bAttrReadonly)
std::filesystem::permissions(pathFile, std::filesystem::perms::owner_write | std::filesystem::perms::group_write |
std::filesystem::perms::others_write, std::filesystem::perm_options::remove);
// Component paths are stored
//if (rsFileDesc.bAttrComponent) vecComponents.push_back(pathFile);
}
}
catch (const std::filesystem::filesystem_error& rexcept)
{
SDV_LOG_ERROR("Cannot create files or directories in \"", pathInstallation, "\": ", rexcept.what());
throw sdv::installation::XIncorrectPath();
}
// TODO: Restriction. Only complex services will be started dynamically and will be added to the configuration. Utilities will
// also be installed but will not be started. Devices and basic services will not be available this way.
// TODO:
// For each component, get the manifest using the manifest helper utility.
// Note: to get the manifest, the component module needs to be loaded and the GetManifest exported function is called. Loading
// a component module causes part of the component to start already and dependencies to be resolved. If this malfunctions, the
// component could crash or do other malicious things. OR if on purpose, an application or complex service component, that
// normally would run in an isolated environment, gets access to the memory and everything else within the process. If this
// module is not isolated from the core, it could access the core and do nasty things. THEREFORE, the manifest helper utility is
// started as a component in its own isolated environment and then loading the component. No component code is executed within
// the core process.
sdv::TObjectPtr ptrManifestUtil = sdv::core::CreateUtility("ManifestHelperUtility");
sdv::helper::IModuleManifestHelper* pManifestHelper = ptrManifestUtil.GetInterface<sdv::helper::IModuleManifestHelper>();
if (!pManifestHelper)
{
SDV_LOG_ERROR("Manifest helper utility cannot be loaded.");
throw sdv::installation::XComponentNotLoadable();
}
// Get the manifest of all components
std::map<std::filesystem::path, sdv::u8string> mapComponentManifests;
for (const std::filesystem::path& rpathComponent : vecComponents)
{
sdv::u8string ssManifest = pManifestHelper->ReadModuleManifest(rpathComponent.generic_u8string());
if (!ssManifest.empty())
mapComponentManifests[rpathComponent] = ssManifest;
}
// Check whether there ary any manifests
if (mapComponentManifests.empty())
{
SDV_LOG_ERROR("No component in installation", ssInstallName, ".");
throw sdv::installation::XNoManifest();
}
// Load the modules
bool bSuccess = true;
for (const auto& rvtModule : mapComponentManifests)
{
sdv::core::TModuleID tModuleID = GetModuleControl().ContextLoad(rvtModule.first, rvtModule.second);
if (!tModuleID)
{
bSuccess = false;
break;
}
// TODO: What about the dependent modules that were started as well. They are not rolled back...
rollback.AddLoadedModule(tModuleID);
}
if (!bSuccess)
{
SDV_LOG_ERROR("Manifest helper utility cannot be loaded.");
throw sdv::installation::XComponentNotLoadable();
}
// Add the installation to the installation map
//SInstallation sInstallation{ seqFiles };
//for (sdv::core::)
//m_mapInstallations[ssInstallName] = SInstallation{ seqFiles };
// Commit the changes - prevent rollback
rollback.Commit();
return true;
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
bool CAppConfig::Update(/*in*/ const sdv::u8string& ssInstallName, /*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles)
{
// TODO: as soon as the configuration is updatable as well, the update uses the same configuration and possibly updates the
// configuration.
// Until the config update feature is available, the update can stop and update and start the installation by uninstall and
// install.
bool bRet = Uninstall(ssInstallName);
if (bRet) bRet = Install(ssInstallName, seqFiles);
return bRet;
}
bool CAppConfig::Uninstall(/*in*/ const sdv::u8string& /*ssInstallName*/)
{
// Installations can only be done in the main application.
if (!GetAppControl().IsMainApplication()) return false;
return false;
}
sdv::sequence<sdv::u8string> CAppConfig::GetInstallations() const
{
return sdv::sequence<sdv::u8string>();
}
sdv::sequence<sdv::installation::SFileDesc> CAppConfig::GetInstallationFiles(/*in*/ const sdv::u8string& /*ssInstallName*/) const
{
return sdv::sequence<sdv::installation::SFileDesc>();
}
std::filesystem::path CAppConfig::FindInstalledModule(const std::filesystem::path& rpathRelModule)
{
// Search for the module in the following order:
// - Core manifest
// - Exe manifest
// - User manifests
std::filesystem::path pathModule = m_manifestCore.FindModule(rpathRelModule);
if (pathModule.empty())
pathModule = m_manifestExe.FindModule(rpathRelModule);
for (const CInstallManifest& rmanifest : m_vecUserManifests)
{
if (!pathModule.empty()) break;
pathModule = rmanifest.FindModule(rpathRelModule);
}
return pathModule;
}
std::string CAppConfig::FindInstalledModuleManifest(const std::filesystem::path& rpathRelModule)
{
// Search for the module in the following order:
// - Core manifest
// - Exe manifest
// - User manifests
std::string ssManifest = m_manifestCore.FindModuleManifest(rpathRelModule);
if (ssManifest.empty())
ssManifest = m_manifestExe.FindModuleManifest(rpathRelModule);
for (const CInstallManifest& rmanifest : m_vecUserManifests)
{
if (!ssManifest.empty()) break;
ssManifest = rmanifest.FindModuleManifest(rpathRelModule);
}
return ssManifest;
}
std::optional<CInstallManifest::SComponent> CAppConfig::FindInstalledComponent(const std::string& rssClass) const
{
// For main and isolated applications, check whether the module is in one of the installation manifests.
if (!GetAppControl().IsMainApplication() && !GetAppControl().IsIsolatedApplication()) return {};
// Search for the component in the following order:
// - Core manifest
// - Exe manifest
// - User manifests
auto optManifest = m_manifestCore.FindComponentByClass(rssClass);
if (!optManifest) optManifest = m_manifestExe.FindComponentByClass(rssClass);
for (const CInstallManifest& rmanifest : m_vecUserManifests)
{
if (optManifest) break;
optManifest = rmanifest.FindComponentByClass(rssClass);
}
return optManifest;
}
void CAppConfig::AddCurrentPath()
{
std::unique_lock<std::mutex> lock(m_mtxSearchPaths);
// Do this only once.
if (!m_lstSearchPaths.empty()) return;
// Add the core directory
std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal();
m_lstSearchPaths.push_back(pathCoreDir / "config/");
// Add the exe dir
std::filesystem::path pathExeDir = GetExecDirectory().lexically_normal();
if (pathExeDir != pathCoreDir) m_lstSearchPaths.push_back(pathExeDir / "config/");
}
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
CAppConfig& CAppConfigService::GetAppConfig()
{
return ::GetAppConfig();
}
bool CAppConfigService::EnableAppConfigAccess()
{
return GetAppControl().IsStandaloneApplication() ||
GetAppControl().IsEssentialApplication();
}
bool CAppConfigService::EnableAppInstallAccess()
{
return GetAppControl().IsMainApplication();
}
#endif // !defined DO_NOT_INCLUDE_IN_UNIT_TEST

View File

@@ -0,0 +1,299 @@
#ifndef APP_CONFIG_H
#define APP_CONFIG_H
#include "app_control.h"
#include "installation_manifest.h"
#include <interfaces/config.h>
#include <mutex>
#include <list>
#include <filesystem>
#include <vector>
#include <map>
// @cond DOXYGEN_IGNORE
// Components are installed using two procedures:
// Procedure 1: by loading a configuration file
// mainly done when running standalone or in essential mode
// Procedure 2: by installing using an interface
// mainly done when running as server
//
// The installation of components must be executed using a separated process to ensure the components are not interfering with
// the main process during installation.
// The component information is stored in the manifest, exposed through an exported function, and read during installation and
// instantiation.
// Installed files have attributes: creation and change dates, executable file flag, CRC, relative sub-directory, compoonent flag.
// The installation might need companion files to be installed in various relative sub-directories.
// @endcond
/**
* @brief Application configuration service
* @details In the configuration system objects, devices, basic services, complex services and apps are defined and will be started
* suring the load process. The objects are loaded in this order (system objects first, apps last) unless dependencies define
* a different order. Utilities, proxy and stub objects are not specified here, since they can be started 'on-the-fly' if needed.
* Loading a configuration extends the current configuration. Saving a configuration includes all objects since the last
* configuration not including the components present before the last load.
*
* The configuration file is a TOML file with the following format:
* @code
* [Configuration]
* Version = 100 # Configuration file version.
*
* [[Component]]
* Path = "my_module.sdv # Relative to the executable or absolute path to the module - not used for main and isolated
* # applications since the components must be installed.
* Class = "MyComponent" # Class name of the component.
* Name = "MyPersonalComponent" # Optional instance name of the component. If not provided, the name will automatically be the
* # default name of the component or if not available the class name of the component.
* AttributeXYZ = 123 # Additional settings for the component provided during initialization.
*
* [[Component]]
* Class = "MyComponent2" # Class name of the component - if also present in "my_module.sdv" doesn't need additional
* # 'Path' value. The component name is taken from the default name of the component.
*
* [[Component]]
* Class = "MyComponent" # Class name of the component; class is the same as before.
* Name = "MyPersonalComponent2" # Specific name identifying another instance of the component.
*
* [[Module]]
* Path = "my_other_module.sdv # Relative to the executable or absolute path to the module - not used for main and isolated
* # applications since the components must be installed. This path might contain components not
* # started, but known by the repository (utilities).
* @endcode
*
* For the main application there are several configuration files:
* - Platform configuration (OS support, middleware support, vehicle bus and Ethernet interface configuration - system objects)
* - Vehicle interface configuration (DBC, port assignments - devices)
* - Vehicle abstraction interface configuration (basic services)
* - Application configuration (complex services and applications)
*
* If components are added dynamically (or removed dynamically) they are added to and removed from the application configuration.
* The configuration files are located in the installation directory.
*
* TODO: Add additional interface function: set component operation mode - config, running...
* When switching to config mode, switch the apps, complex services, basic services, devices and system objects (in that order) to config mode.
* When switching back to running mode, enable running mode for the system objects, devices, base services, complex services and apps (in that order).
*
*/
class CAppConfig : public sdv::IInterfaceAccess, public sdv::core::IConfig, public sdv::installation::IAppInstall
{
public:
CAppConfig() = default;
// Interface map
// Note: only add globally accessible interfaces here (which are not limited by the type of application).
BEGIN_SDV_INTERFACE_MAP()
END_SDV_INTERFACE_MAP()
/**
* @brief Load the installation manifests for core, executable and user components. Main and isolated applications only.
* @return Returns 'true when successful; 'false' when not.
*/
bool LoadInstallationManifests();
/**
* @brief Unload all manifest during shutdown.
*/
void UnloadInstallatonManifests();
/**
* @brief Process the provided configuration by loading modules and creating objects/stubs/proxies defined in the
* configuration string. Processing a configuration resets the baseline before processing. Overload of
* sdv::core::IConfig::ProcessConfig.
* @attention Configuration changes can only occur when the system is in configuration mode.
* @param[in] ssContent The contents of the configuration file (TOML).
* @return Returns a config process result enum value.
*/
virtual sdv::core::EConfigProcessResult ProcessConfig(/*in*/ const sdv::u8string& ssContent) override;
/**
* @brief Read file pointed to by the provided file path and load modules and create objects/stubs/proxies as defined
* in the configuration file. Loading a configuration resets the baseline before processing. Overload of
* sdv::core::IConfig::LoadConfig.
* @attention Configuration changes can only occur when the system is in configuration mode.
* @param[in] ssConfigPath Path to the file containing the configuration (TOML). The path can be an absolute as well as a
* relative path. In case a relative path is provided, the current directory is searched as well as all directories
* supplied through the AddConfigSearchDir function.
* @return Returns a config process result enum value.
*/
virtual sdv::core::EConfigProcessResult LoadConfig(/*in*/ const sdv::u8string& ssConfigPath) override;
/**
* @brief Save a configuration file pointed to by the provided file path. All components are saved that were added after
* the last baseline with the configuration specific settings. Overload of sdv::core::IConfig::SaveConfig.
* @attention Configuration changes can only occur when the system is in configuration mode.
* @param[in] ssConfigPath Path to the file containing the configuration (TOML). The path can be an absolute as well as a
* relative path. In case a relative path is provided, the configuration is stored relative to the executable directory.
* @return Returns 'true' on success; 'false' otherwise.
*/
virtual bool SaveConfig(/*in*/ const sdv::u8string& ssConfigPath) const override;
/**
* @brief Add a search path to a folder where a config file can be found. Overload of
* sdv::core::IConfig::AddDirectorySearchDir.
* @param[in] ssDir Relative or absolute path to an existing folder.
* @return Returns 'true' on success; 'false' otherwise.
*/
virtual bool AddConfigSearchDir(/*in*/ const sdv::u8string& ssDir) override;
/**
* @brief Reset the configuration baseline.
* @details The configuration baseline determines what belongs to the current configuration. Any object being added
* after this baseline will be stored in a configuration file.
*/
virtual void ResetConfigBaseline() override;
/**
* @brief Make an installation onto the system. Overload of sdv::installation::IAppInstall::Install.
* @details Make an installation with from component modules and supporting files. At least one component module must
* be provided for this installation to be successful (component attribute flag must be set). Components are only
* installed, not activated.
* @remarks The system needs to run in configuration mode.
* @param[in] ssInstallName Name of the installation. Must be unique to the system.
* @param[in] seqFiles Files belonging to the installation. This could be component modules as well as supporting files.
* @return Returns whether the installation was successful - installation name was unique and at least one loadable
* component was provided.
*/
virtual bool Install(/*in*/ const sdv::u8string& ssInstallName,
/*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles) override;
/**
* @brief Update an installation. Overload of sdv::installation::IAppInstall::Update.
* @details Stops a component if the component is running, makes an update and if the component was running, starts
* the component again.
* @todo: Currently limited to complex services only.
* @remarks The system needs to run in configuration mode.
* @param[in] ssInstallName Name of the installation. Must be unique to the system.
* @param[in] seqFiles Files belonging to the installation. This could be component modules as well as supporting files.
* @return Returns whether the installation was successful - installation name was unique and at least one loadable
* component was provided.
*/
virtual bool Update(/*in*/ const sdv::u8string& ssInstallName,
/*in*/ const sdv::sequence<sdv::installation::SFileDesc>& seqFiles) override;
/**
* @brief Uninstall of a previous installation. Overload of sdv::installation::IAppInstall::Uninstall.
* @details Stops a component if the component is running and removes the files and modules.
* @todo: Currently limited to complex services only.
* @remarks The system needs to run in configuration mode.
* @param[in] ssInstallName Installation name.
* @return Returns whether the uninstallation was successful.
*/
virtual bool Uninstall(/*in*/ const sdv::u8string& ssInstallName) override;
/**
* @brief Get a sequence of installations. Overload of sdv::installation::IAppInstall::GetInstallations.
* @return The sequence with installations.
*/
virtual sdv::sequence<sdv::u8string> GetInstallations() const override;
/**
* @brief Get the installed files from the installation. Overload of sdv::installation::IAppInstall::GetInstallationFiles.
* @param[in] ssInstallName The installation to get the files for.
* @return Sequence containing the file structures without the binary file content.
*/
virtual sdv::sequence<sdv::installation::SFileDesc> GetInstallationFiles(/*in*/ const sdv::u8string& ssInstallName) const override;
/**
* @brief Find the module stored in the installation manifest (core, exe or user installations).
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
* @return Returns the full path if the module was found or an empty path when not.
*/
std::filesystem::path FindInstalledModule(const std::filesystem::path& rpathRelModule);
/**
* @brief Find the module stored in the installation manifest (core, exe or user installations).
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
* @return Returns the module manifest if the module was found or an empty string when not.
*/
std::string FindInstalledModuleManifest(const std::filesystem::path& rpathRelModule);
/**
* @brief Search for the installed component with the specific class name.
* @details Find the first component containing a class with the specified name. For main and isolated applications.
* The order of checking the installation manifest is core-manifest, manifest in executable directory and manifest in supplied
* installation directory.
* @remarks Components of system objects specified in the user installation are not returned.
* @param[in] rssClass Reference to the class that should be searched for. The class is compared to the class name and the
* default name in the manifest.
* @return Optionally returns the component manifest.
*/
std::optional<CInstallManifest::SComponent> FindInstalledComponent(const std::string& rssClass) const;
private:
/**
* @brief Add config folders of the core_services and the executable to the search paths if not done so already.
*/
void AddCurrentPath();
/**
* @brief Installation structure
*/
struct SInstallation
{
sdv::sequence<sdv::installation::SFileDesc> seqFiles; ///< Companion files
};
std::mutex m_mtxSearchPaths; ///< Access protection to directory list.
std::list<std::filesystem::path> m_lstSearchPaths; ///< List of search directories.
std::filesystem::path m_pathLastSearchDir; ///< The last search directory to also save the file to.
std::mutex m_mtxInstallations; ///< Access protection to the installations.
CInstallManifest m_manifestCore; ///< Install manifest for core components (main and isolated apps).
CInstallManifest m_manifestExe; ///< Install manifest for exe components (main and isolated apps).
std::vector<CInstallManifest> m_vecUserManifests; ///< Install manifests for user components (main and isolated apps).
std::map<std::string, SInstallation> m_mapInstallations; ///< Installation map.
};
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
/**
* @brief App config service class.
*/
class CAppConfigService : public sdv::CSdvObject
{
public:
/**
* @brief Default constructor
*/
CAppConfigService() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_CHAIN_MEMBER(GetAppConfig())
SDV_INTERFACE_SET_SECTION_CONDITION(EnableAppConfigAccess(), 1)
SDV_INTERFACE_SECTION(1)
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IConfig, GetAppConfig())
SDV_INTERFACE_DEFAULT_SECTION()
SDV_INTERFACE_SET_SECTION_CONDITION(EnableAppInstallAccess(), 2)
SDV_INTERFACE_SECTION(2)
SDV_INTERFACE_ENTRY_MEMBER(sdv::installation::IAppInstall, GetAppConfig())
SDV_INTERFACE_DEFAULT_SECTION()
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_NAME("ConfigService")
DECLARE_OBJECT_SINGLETON()
/**
* @brief Get access to the application config.
* @return Returns the one global instance of the application config.
*/
static CAppConfig& GetAppConfig();
/**
* @brief When set, the application control access will be enabled.
* @return Returns 'true' when the access to the application configuration is granted; otherwise returns 'false'.
*/
static bool EnableAppConfigAccess();
/**
* @brief When set, the application install will be enabled.
* @return Returns whether access to the application install is granted.
*/
static bool EnableAppInstallAccess();
};
DEFINE_SDV_OBJECT_NO_EXPORT(CAppConfigService)
#endif
#endif // !defined APP_CONFIG_H

View File

@@ -0,0 +1,975 @@
#include "app_control.h"
#include "module_control.h"
#include "repository.h"
#include "sdv_core.h"
#include "../../global/exec_dir_helper.h"
#include "../../global/base64.h"
#include "../../global/flags.h"
#include "../../global/tracefifo/trace_fifo.cpp"
#include "toml_parser/parser_toml.h"
#include "local_shutdown_request.h"
const char* szSettingsTemplate = R"code(# Settings file
[Settings]
Version = 100
# The system config array can contain zero or more configurations that are loaded at the time
# the system ist started. It is advisable to split the configurations in:
# platform config - containing all the components needed to interact with the OS,
# middleware, vehicle bus, Ethernet.
# vehicle interface - containing the vehicle bus interpretation components like data link
# based on DBC and devices for their abstraction.
# vehicle abstraction - containing the basic services
# Load the system configurations by providing the "SystemConfig" keyword as an array of strings.
# A relative path is relative to the installation directory (being "exe_location/instance_id").
#
# Example:
# SystemConfig = [ "platform.toml", "vehicle_ifc.toml", "vehicle_abstract.toml" ]
#
# The application config contains the configuration file that can be updated when services and
# apps are being added to the system (or being removed from the system). Load the application
# config by providing the "AppConfig" keyword as a string value. A relative path is relative to
# the installation directory (being "exe_location/instance_id").
#
# Example
# AppConfig = "app_config.toml"
)code";
/**
* @brief Specific exit handler helping to shut down after startup. In case the shutdown wasn't explicitly executed.
*/
void ExitHandler()
{
// Check if the system was properly shut down
switch (GetAppControl().GetOperationState())
{
case sdv::app::EAppOperationState::shutting_down:
case sdv::app::EAppOperationState::not_started:
break;
default:
GetAppControl().Shutdown(true);
break;
}
}
CAppControl::CAppControl()
{}
CAppControl::~CAppControl()
{}
bool CAppControl::IsMainApplication() const
{
return m_eContextMode == sdv::app::EAppContext::main;
}
bool CAppControl::IsIsolatedApplication() const
{
return m_eContextMode == sdv::app::EAppContext::isolated;
}
bool CAppControl::IsStandaloneApplication() const
{
return m_eContextMode == sdv::app::EAppContext::standalone;
}
bool CAppControl::IsEssentialApplication() const
{
return m_eContextMode == sdv::app::EAppContext::essential;
}
bool CAppControl::IsMaintenanceApplication() const
{
return m_eContextMode == sdv::app::EAppContext::maintenance;
}
bool CAppControl::IsExternalApplication() const
{
return m_eContextMode == sdv::app::EAppContext::external;
}
sdv::app::EAppContext CAppControl::GetContextType() const
{
return m_eContextMode;
}
uint32_t CAppControl::GetInstanceID() const
{
return m_uiInstanceID;
}
uint32_t CAppControl::GetRetries() const
{
return m_uiRetries;
}
bool CAppControl::Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfaceAccess* pEventHandler)
{
m_pEvent = pEventHandler ? pEventHandler->GetInterface<sdv::app::IAppEvent>() : nullptr;
// Intercept the logging...
std::stringstream sstreamCOUT, sstreamCLOG, sstreamCERR;
std::streambuf* pstreambufCOUT = std::cout.rdbuf(sstreamCOUT.rdbuf());
std::streambuf* pstreambufCLOG = std::clog.rdbuf(sstreamCLOG.rdbuf());
std::streambuf* pstreambufCERR = std::cerr.rdbuf(sstreamCERR.rdbuf());
// Write initial startup message
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
SDV_LOG_INFO("SDV Application start at ", std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S %Z"));
BroadcastOperationState(sdv::app::EAppOperationState::initializing);
// Process the application config.
bool bRet = ProcessAppConfig(ssConfig);
// Undo logging interception
sstreamCOUT.rdbuf()->pubsync();
sstreamCLOG.rdbuf()->pubsync();
sstreamCERR.rdbuf()->pubsync();
std::cout.rdbuf(pstreambufCOUT);
std::clog.rdbuf(pstreambufCLOG);
std::cerr.rdbuf(pstreambufCERR);
// Process config error...
if (!bRet)
{
std::cout << sstreamCOUT.str();
std::clog << sstreamCLOG.str();
std::cerr << sstreamCERR.str();
if (!m_bSilent)
std::cerr << "ERROR: Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Open the stream buffer and attach the streams if the application control is initialized as main app.
if (m_eContextMode == sdv::app::EAppContext::main)
{
m_fifoTraceStreamBuffer.SetInstanceID(m_uiInstanceID);
m_fifoTraceStreamBuffer.Open(1000);
std::cout << "**********************************************" << std::endl;
}
// Stream the starting logging to the output streams...
std::cout << sstreamCOUT.str();
std::clog << sstreamCLOG.str();
std::cerr << sstreamCERR.str();
// Check for a correctly opened stream buffer
if (m_eContextMode == sdv::app::EAppContext::main && !m_fifoTraceStreamBuffer.IsOpened())
{
if (!m_bSilent)
std::cerr << "ERROR: Log streaming could not be initialized; cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Allow only one instance if running as main application
if (m_eContextMode == sdv::app::EAppContext::main)
{
m_pathLockFile = GetExecDirectory() / ("sdv_core_" + std::to_string(m_uiInstanceID) + ".lock");
#ifdef _WIN32
// Checkf or the existence of a lock file. If existing, do not continue.
if (std::filesystem::exists(m_pathLockFile)) return false;
// Create temp file
m_pLockFile = _wfopen(m_pathLockFile.native().c_str(), L"a+TD");
#elif defined __unix__
// Create a lock file if not existing.
m_pLockFile = fopen(m_pathLockFile.native().c_str(), "a+");
if (!m_pLockFile || lockf(fileno(m_pLockFile), F_TLOCK, 0) != 0)
{
// File exists already or could not be opened.
// No continuation possible.
if (m_pLockFile) fclose(m_pLockFile);
m_pLockFile = nullptr;
return false;
}
#else
#error OS is not supported!
#endif
}
std::string ssErrorString;
auto fnLoadModule = [&ssErrorString](const sdv::u8string& rssModule) -> bool
{
bool bLocalRet = GetModuleControl().Load(rssModule);
if (!bLocalRet) ssErrorString = std::string("Failed to load module '") + rssModule + "'.";
return bLocalRet;
};
auto fnCreateObject = [&ssErrorString](const sdv::u8string& rssClass, const sdv::u8string& rssObject, const sdv::u8string& rssConfig) -> bool
{
bool bLocalRet = GetRepository().CreateObject2(rssClass, rssObject, rssConfig);
if (!bLocalRet)
{
ssErrorString = std::string("Failed to instantiate a new object from class '") + rssClass + "'";
if (!rssObject.empty())
ssErrorString += std::string(" with name '") + rssObject + "'.";
}
return bLocalRet;
};
// Load the core services
if (!fnLoadModule("core_services.sdv"))
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to load the Core Services. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Load the logger module if one is specified
if (!m_pathLoggerModule.empty())
{
m_tLoggerModuleID = fnLoadModule(m_pathLoggerModule.u8string());
if (!m_tLoggerModuleID)
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to load the custom logger. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
}
// Start the logger and assign it to the logger control.
fnCreateObject(m_ssLoggerClass, m_ssLoggerClass, "");
sdv::IInterfaceAccess* pLoggerObj = GetRepository().GetObject(m_ssLoggerClass);
if (!pLoggerObj)
{
GetRepository().DestroyObject2(m_ssLoggerClass);
if (!m_bSilent)
std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
sdv::core::ILoggerConfig* pLoggerConfig = pLoggerObj->GetInterface<sdv::core::ILoggerConfig>();
sdv::core::ILogger* pLogger = pLoggerObj->GetInterface<sdv::core::ILogger>();
if (!pLoggerConfig || !pLogger)
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to start the logger. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
if (!m_ssProgramTag.empty())
pLoggerConfig->SetProgramTag(m_ssProgramTag);
pLoggerConfig->SetLogFilter(m_eSeverityFilter, m_eSeverityViewFilter);
GetLoggerControl().SetLogger(pLogger);
// Create the core service objects
bRet = fnCreateObject("AppControlService", "AppControlService", "");
bRet = bRet && fnCreateObject("RepositoryService", "RepositoryService", "");
bRet = bRet && fnCreateObject("ModuleControlService", "ModuleControlService", "");
bRet = bRet && fnCreateObject("ConfigService", "ConfigService", "");
if (!bRet)
{
if (!m_bSilent)
{
std::cerr << "ERROR: Failed to start the Core Services. Cannot continue!" << std::endl;
if (!ssErrorString.empty())
std::cerr << "REASON: " << ssErrorString << std::endl;
}
Shutdown(true);
return false;
}
// Load specific services
bool bLoadRPCClient = false, bLoadRPCServer = false;
bool bLoadInstallationManifests = false;
switch (m_eContextMode)
{
case sdv::app::EAppContext::standalone:
break;
case sdv::app::EAppContext::external:
bLoadRPCClient = true;
break;
case sdv::app::EAppContext::isolated:
bLoadInstallationManifests = true;
bLoadRPCClient = true;
break;
case sdv::app::EAppContext::main:
bLoadInstallationManifests = true;
bLoadRPCClient = true;
bLoadRPCServer = true;
break;
case sdv::app::EAppContext::essential:
//bLoadRPCClient = true;
break;
case sdv::app::EAppContext::maintenance:
bLoadRPCClient = true;
break;
default:
if (!m_bSilent)
std::cerr << "ERROR: Invalid Run-As mode. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
// Load installation manifests for main and isolated applications.
if (bLoadInstallationManifests)
{
if (!GetAppConfig().LoadInstallationManifests())
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to load installation manifests. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
}
// Load process control
if (bRet) bRet = fnLoadModule("process_control.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ProcessControlService", "", "");
// Load RPC components
if (bRet && bLoadRPCServer)
{
// Load hardware identification
bRet = fnLoadModule("hardware_ident.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("HardwareIdentificationService", "", "");
// Load shared memory channel
if (bRet) bRet = fnLoadModule("ipc_shared_mem.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("DefaultSharedMemoryChannelControl", "", "");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_com.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("CommunicationControl", "", "");
// Load IPC service and create listener local connections
if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ConnectionListenerService", "", R"code([Listener]
Type = "Local"
)code");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ConnectionService", "", "");
// Load proxy/stub for core interfaces
if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false;
// // Start the listener
// if (bRet) bRet = fnLoadModule("ipc_listener.sdv") ? true : false;
// if (bRet) bRet = GetRepository().CreateObject("ConnectionListenerService", "ConnectionListenerService", R"code([Listener]
//Type="local"
//Instance=)code" + std::to_string(GetInstanceID()));
if (!bRet)
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to load IPC server components. Cannot continue!" << std::endl;
if (!ssErrorString.empty())
std::cerr << "REASON: " << ssErrorString << std::endl;
Shutdown(true);
return false;
}
}
else if (bRet && bLoadRPCClient)
{
// Interpret the connect string...
// Load hardware identification
bRet = fnLoadModule("hardware_ident.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("HardwareIdentificationService", "", "");
// Load shared memory channel
if (bRet) bRet = fnLoadModule("ipc_shared_mem.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("DefaultSharedMemoryChannelControl", "", "");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_com.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("CommunicationControl", "", "");
// Load IPC service
if (bRet) bRet = fnLoadModule("ipc_connect.sdv") ? true : false;
if (bRet) bRet = fnCreateObject("ConnectionService", "", "");
// Load proxy/stub for core interfaces
if (bRet) bRet = fnLoadModule("core_ps.sdv") ? true : false;
if (!bRet)
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to load IPC client components. Cannot continue!" << std::endl;
Shutdown(true);
return false;
}
}
if (!bRet)
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to start core components. Cannot continue!" << std::endl;
if (!ssErrorString.empty())
std::cerr << "REASON: " << ssErrorString << std::endl;
Shutdown(true);
return false;
}
// Register the exit handler to force a proper shutdown in case the application exits without a call to shutdown.
std::atexit(ExitHandler);
BroadcastOperationState(sdv::app::EAppOperationState::initialized);
SetConfigMode();
if (IsMainApplication())
{
// Load system configs - they need to be present completely
for (const std::filesystem::path& rpathConfig : m_vecSysConfigs)
{
sdv::core::EConfigProcessResult eResult = GetAppConfig().LoadConfig(rpathConfig.generic_u8string());
if (eResult != sdv::core::EConfigProcessResult::successful)
{
if (!m_bSilent)
std::cerr << "ERROR: Cannot load or partly load the system configuration: " <<
rpathConfig.generic_u8string() << std::endl;
Shutdown(true);
return false;
}
}
// The system configs should not be stored once more.
GetAppConfig().ResetConfigBaseline();
// Load the application config - they can also be partly there
if (!m_pathAppConfig.empty())
{
sdv::core::EConfigProcessResult eResult = GetAppConfig().LoadConfig(m_pathAppConfig.generic_u8string());
if (eResult == sdv::core::EConfigProcessResult::failed)
{
if (!m_bSilent)
std::cerr << "ERROR: Cannot load application configuration: " << m_pathAppConfig.generic_u8string() << std::endl;
Shutdown(true);
return false;
}
else if (eResult != sdv::core::EConfigProcessResult::partially_successful)
{
if (!m_bSilent)
std::cerr << "WARNING: Partially could not load application configuration: " <<
m_pathAppConfig.generic_u8string() << std::endl;
}
}
}
SetRunningMode();
return true;
}
void CAppControl::RunLoop()
{
// Only allowed to run during configuration and running mode.
switch (m_eState)
{
case sdv::app::EAppOperationState::configuring :
case sdv::app::EAppOperationState::running :
break;
default:
throw sdv::XInvalidState();
}
// Treat local running differently from main and isolated app running.
// Note... Maintenance apps should not use the loop
bool bLocal = true;
switch (m_eContextMode)
{
case sdv::app::EAppContext::main:
case sdv::app::EAppContext::isolated:
bLocal = false;
break;
case sdv::app::EAppContext::maintenance:
throw sdv::XAccessDenied();
break;
default:
bLocal = true;
break;
}
CShutdownRequestListener listener;
if (bLocal)
{
listener = std::move(CShutdownRequestListener(m_uiInstanceID));
if (!listener.IsValid())
throw sdv::XAccessDenied(); // Another instance is already running.
}
if (m_bVerbose)
std::cout << "Entering loop" << std::endl;
m_bRunLoop = true;
while (m_bRunLoop)
{
// Check and wait 2 milliseconds.
if (bLocal)
m_bRunLoop = !listener.HasTriggered(2);
else
std::this_thread::sleep_for(std::chrono::milliseconds(2));
// Inform about running.
if (m_pEvent)
{
sdv::app::SAppEvent sEvent{};
sEvent.uiEventID = sdv::app::EVENT_RUNNING_LOOP;
sEvent.uiInfo = 0u;
m_pEvent->ProcessEvent(sEvent);
}
}
if (m_bVerbose)
std::cout << "Leaving loop" << std::endl;
}
void CAppControl::Shutdown(/*in*/ bool bForce)
{
if (m_eState == sdv::app::EAppOperationState::not_started) return;
// Shutdown the running loop
if (m_bRunLoop)
{
m_bRunLoop = false;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
BroadcastOperationState(sdv::app::EAppOperationState::shutting_down);
// Set the config state for all objects
GetRepository().SetConfigMode();
// Disable automatic configuration saving.
m_bAutoSaveConfig = false;
// Destroy all objects... this should also remove any registered services, except the custom logger.
GetRepository().DestroyAllObjects(std::vector<std::string>({ m_ssLoggerClass }), bForce);
// Unload all modules... this should destroy all running services, except the custom logger
GetModuleControl().UnloadAll(std::vector<sdv::core::TModuleID>({ m_tLoggerModuleID }));
// Remove all installation manifests
GetAppConfig().UnloadInstallatonManifests();
// End logging
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
if (bForce) SDV_LOG_INFO("Forced shutdown of application.");
SDV_LOG_INFO("SDV Application end at ", std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S %Z"));
GetLoggerControl().SetLogger(nullptr);
// Unload all modules... this should also destroy the custom logger.
GetModuleControl().UnloadAll(std::vector<sdv::core::TModuleID>());
// Release the lock on the instance (only for main application)
if (m_pLockFile)
{
#ifdef __unix__
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
#endif
lockf(fileno(m_pLockFile), F_ULOCK, 0);
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#endif
fclose(m_pLockFile);
m_pLockFile = nullptr;
try
{
if (std::filesystem::exists(m_pathLockFile))
std::filesystem::remove(m_pathLockFile);
} catch (const std::filesystem::filesystem_error&)
{}
m_pathLockFile.clear();
}
// End trace streaming
if (m_eContextMode == sdv::app::EAppContext::main)
{
std::cout << "**********************************************" << std::endl;
// Shutdown log streaming
m_fifoTraceStreamBuffer.Close();
}
BroadcastOperationState(sdv::app::EAppOperationState::not_started);
m_eContextMode = sdv::app::EAppContext::no_context;
m_uiInstanceID = 0u;
m_pEvent = nullptr;
m_ssLoggerClass.clear();
m_tLoggerModuleID = 0;
m_pathLoggerModule.clear();
m_ssProgramTag.clear();
m_eSeverityFilter = sdv::core::ELogSeverity::info;
m_pathInstallDir.clear();
m_pathRootDir.clear();
m_vecSysConfigs.clear();
m_pathAppConfig.clear();
m_bAutoSaveConfig = false;
m_bEnableAutoSave = false;
m_bSilent = false;
m_bVerbose = false;
}
void CAppControl::RequestShutdown()
{
SDV_LOG_INFO("Shutdown request received.");
m_bRunLoop = false;
}
sdv::app::EAppOperationState CAppControl::GetOperationState() const
{
return m_eState;
}
uint32_t CAppControl::GetInstance() const
{
return m_uiInstanceID;
}
void CAppControl::SetConfigMode()
{
GetRepository().SetConfigMode();
if (m_eState == sdv::app::EAppOperationState::running || m_eState == sdv::app::EAppOperationState::initialized)
BroadcastOperationState(sdv::app::EAppOperationState::configuring);
}
void CAppControl::SetRunningMode()
{
GetRepository().SetRunningMode();
if (m_eState == sdv::app::EAppOperationState::configuring || m_eState == sdv::app::EAppOperationState::initialized)
BroadcastOperationState(sdv::app::EAppOperationState::running);
}
sdv::sequence<sdv::u8string> CAppControl::GetNames() const
{
sdv::sequence<sdv::u8string> seqNames = {"app.instance_id", "console.info_level"};
return seqNames;
}
sdv::any_t CAppControl::Get(/*in*/ const sdv::u8string& ssAttribute) const
{
if (ssAttribute == "app.instance_id") return sdv::any_t(m_uiInstanceID);
if (ssAttribute == "console.info_level")
{
if (m_bSilent) return "silent";
if (m_bVerbose) return "verbose";
return "normal";
}
return {};
}
bool CAppControl::Set(/*in*/ const sdv::u8string& /*ssAttribute*/, /*in*/ sdv::any_t /*anyAttribute*/)
{
// Currently there are not setting attributes...
return false;
}
uint32_t CAppControl::GetFlags(/*in*/ const sdv::u8string& ssAttribute) const
{
if (ssAttribute == "app.instance_id") return hlpr::flags<sdv::EAttributeFlags>(sdv::EAttributeFlags::read_only);
if (ssAttribute == "console.info_level") return hlpr::flags<sdv::EAttributeFlags>(sdv::EAttributeFlags::read_only);
return 0u;
}
std::filesystem::path CAppControl::GetInstallDir() const
{
return m_pathInstallDir;
}
void CAppControl::DisableAutoConfigUpdate()
{
m_bEnableAutoSave = false;
}
void CAppControl::EnableAutoConfigUpdate()
{
m_bEnableAutoSave = true;
}
void CAppControl::TriggerConfigUpdate()
{
if (!m_bAutoSaveConfig || !m_bEnableAutoSave) return;
if (m_pathAppConfig.empty()) return;
if (!GetAppConfig().SaveConfig(m_pathAppConfig.generic_u8string()))
SDV_LOG_ERROR("Failed to automatically save the configuration ", m_pathAppConfig.generic_u8string());
}
bool CAppControl::IsConsoleSilent() const
{
return m_bSilent;
}
bool CAppControl::IsConsoleVerbose() const
{
return m_bVerbose;
}
void CAppControl::BroadcastOperationState(sdv::app::EAppOperationState eState)
{
m_eState = eState;
if (m_pEvent)
{
sdv::app::SAppEvent sEvent{};
sEvent.uiEventID = sdv::app::EVENT_OPERATION_STATE_CHANGED;
sEvent.uiInfo = static_cast<uint32_t>(eState);
m_pEvent->ProcessEvent(sEvent);
}
}
bool CAppControl::ProcessAppConfig(const sdv::u8string& rssConfig)
{
CParserTOML parserConfig;
std::string ssError;
try
{
// Read the configuration
if (!parserConfig.Process(rssConfig)) return false;
} catch (const sdv::toml::XTOMLParseException& rexcept)
{
ssError = std::string("ERROR: Failed to parse application configuration: ") + rexcept.what();
}
// Get the reporting settings (if this succeeded at all...)
auto ptrReport = parserConfig.GetRoot().GetDirect("Console.Report");
if (ptrReport && ptrReport->GetValue() == "Silent") m_bSilent = true;
if (ptrReport && ptrReport->GetValue() == "Verbose") m_bVerbose = true;
// Report the outstanding error (if there is one...)
if (!ssError.empty())
{
if (!m_bSilent)
std::cerr << ssError << std::endl;
return false;
}
// Allow a custom logger to be defined
std::filesystem::path pathLoggerModule;
std::string ssLoggerClass;
std::shared_ptr<CNode> ptrLogHandlerPath = parserConfig.GetRoot().GetDirect("LogHandler.Path");
std::shared_ptr<CNode> ptrLogHandlerClass = parserConfig.GetRoot().GetDirect("LogHandler.Class");
if (ptrLogHandlerPath && !ptrLogHandlerClass)
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to process application log: custom logger handler module path supplied, but no class "
"defined!" << std::endl;
return false;
}
if (!ptrLogHandlerPath && ptrLogHandlerClass)
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to process application log: custom logger handler class supplied, but no module "
"defined!" << std::endl;
return false;
}
if (ptrLogHandlerPath)
{
m_pathLoggerModule = static_cast<std::string>(ptrLogHandlerPath->GetValue());
m_ssLoggerClass = static_cast<std::string>(ptrLogHandlerClass->GetValue());
} else
{
// Default logger
m_ssLoggerClass = "DefaultLoggerService";
}
// Get an optional program tag for the logger
std::shared_ptr<CNode> ptrLogPogramTag = parserConfig.GetRoot().GetDirect("LogHandler.Tag");
if (ptrLogPogramTag) m_ssProgramTag = static_cast<std::string>(ptrLogPogramTag->GetValue());
// Get the application-mode
std::string ssApplication = "Standalone";
std::shared_ptr<CNode> ptrApplication = parserConfig.GetRoot().GetDirect("Application.Mode");
if (ptrApplication) ssApplication = static_cast<std::string>(ptrApplication->GetValue());
if (ssApplication == "Standalone") m_eContextMode = sdv::app::EAppContext::standalone;
else if (ssApplication == "External") m_eContextMode = sdv::app::EAppContext::external;
else if (ssApplication == "Isolated") m_eContextMode = sdv::app::EAppContext::isolated;
else if (ssApplication == "Main") m_eContextMode = sdv::app::EAppContext::main;
else if (ssApplication == "Essential") m_eContextMode = sdv::app::EAppContext::essential;
else if (ssApplication == "Maintenance") m_eContextMode = sdv::app::EAppContext::maintenance;
else
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to process startup config: invalid application-mode specified for core library: " <<
ssApplication << std::endl;
return false;
}
// Get the severity level filter for the logger
auto fnTranslateSevFilter = [this](const std::string& rssLogFilter, const sdv::core::ELogSeverity eDefault)
{
sdv::core::ELogSeverity eSeverityFilter = eDefault;
if (rssLogFilter == "Trace") eSeverityFilter = sdv::core::ELogSeverity::trace;
else if (rssLogFilter == "Debug") eSeverityFilter = sdv::core::ELogSeverity::debug;
else if (rssLogFilter == "Info") eSeverityFilter = sdv::core::ELogSeverity::info;
else if (rssLogFilter == "Warning") eSeverityFilter = sdv::core::ELogSeverity::warning;
else if (rssLogFilter == "Error") eSeverityFilter = sdv::core::ELogSeverity::error;
else if (rssLogFilter == "Fatal") eSeverityFilter = sdv::core::ELogSeverity::fatal;
else if (!rssLogFilter.empty())
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to process application log: invalid severity level filter: '" << rssLogFilter <<
"'" << std::endl;
}
return eSeverityFilter;
};
sdv::core::ELogSeverity eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::error;
if (IsMainApplication() || IsIsolatedApplication())
eLogDefaultViewSeverityFilter = sdv::core::ELogSeverity::info;
std::shared_ptr<CNode> ptrLogSeverityFilter = parserConfig.GetRoot().GetDirect("LogHandler.Filter");
m_eSeverityFilter = fnTranslateSevFilter(ptrLogSeverityFilter ? ptrLogSeverityFilter->GetValue() : "",
sdv::core::ELogSeverity::info);
ptrLogSeverityFilter = parserConfig.GetRoot().GetDirect("LogHandler.ViewFilter");
m_eSeverityViewFilter = fnTranslateSevFilter(ptrLogSeverityFilter ? ptrLogSeverityFilter->GetValue() : "",
eLogDefaultViewSeverityFilter);
// Get the optional instance ID.
std::shared_ptr<CNode> ptrInstance = parserConfig.GetRoot().GetDirect("Application.Instance");
if (ptrInstance)
m_uiInstanceID = ptrInstance->GetValue();
else
m_uiInstanceID = 1000u;
// Number of attempts to establish a connection to a running instance.
std::shared_ptr<CNode> ptrRetries = parserConfig.GetRoot().GetDirect("Application.Retries");
if (ptrRetries)
{
m_uiRetries = ptrRetries->GetValue();
if (m_uiRetries > 30)
m_uiRetries = 30;
else if (m_uiRetries < 3)
m_uiRetries = 3;
}
// Main and isolated apps specific information.
if (IsMainApplication() || IsIsolatedApplication())
{
// Get the optional installation directory.
std::shared_ptr<CNode> ptrInstallDir = parserConfig.GetRoot().GetDirect("Application.InstallDir");
if (ptrInstallDir)
{
m_pathRootDir = ptrInstallDir->GetValue();
if (m_pathRootDir.is_relative())
m_pathRootDir = GetExecDirectory() / m_pathRootDir;
}
else
m_pathRootDir = GetExecDirectory() / std::to_string(m_uiInstanceID);
m_pathInstallDir = m_pathRootDir;
try
{
std::filesystem::create_directories(m_pathRootDir);
}
catch (const std::filesystem::filesystem_error& rexcept)
{
if (!m_bSilent)
{
std::cerr << "Cannot create root directory: " << m_pathRootDir << std::endl;
std::cerr << " Reason: " << rexcept.what() << std::endl;
}
return false;
}
}
// Maintenance and isolated applications cannot load specific configs. The others can specify a configuration file, but
// not auto-updateable.
if (!IsMaintenanceApplication() && !IsIsolatedApplication())
{
auto ptrConfigFile = parserConfig.GetRoot().GetDirect("Application.Config");
if (ptrConfigFile)
m_pathAppConfig = ptrConfigFile->GetValue();
}
// Read the settings... if existing. And only for the main application
if (IsMainApplication())
{
// If the template is not existing, create the template...
if (!std::filesystem::exists(m_pathRootDir / "settings.toml"))
{
std::ofstream fstream(m_pathRootDir / "settings.toml");
if (!fstream.is_open())
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to store application settings." << std::endl;
return false;
}
fstream << szSettingsTemplate;
fstream.close();
}
else
{
std::ifstream fstream(m_pathRootDir / "settings.toml");
std::string ssContent((std::istreambuf_iterator<char>(fstream)), std::istreambuf_iterator<char>());
try
{
// Read the configuration
CParserTOML parserSettings(ssContent);
// Check for the version
auto ptrVersion = parserSettings.GetRoot().GetDirect("Settings.Version");
if (!ptrVersion)
{
if (!m_bSilent)
std::cerr << "ERROR: Missing version in application settings file." << std::endl;
return false;
}
if (ptrVersion->GetValue() != SDVFrameworkInterfaceVersion)
{
if (!m_bSilent)
std::cerr << "ERROR: Invalid version of application settings file (expected version " <<
SDVFrameworkInterfaceVersion << ", but available version " <<
static_cast<uint32_t>(ptrVersion->GetValue()) << ")" << std::endl;
return false;
}
// Read the system configurations
auto ptrSystemConfigs = parserSettings.GetRoot().GetDirect("Settings.SystemConfig");
if (ptrSystemConfigs && ptrSystemConfigs->GetArray())
{
for (uint32_t uiIndex = 0; uiIndex < ptrSystemConfigs->GetArray()->GetCount(); uiIndex++)
{
auto ptrSystemConfig = ptrSystemConfigs->GetArray()->Get(uiIndex);
if (!ptrSystemConfig) continue;
m_vecSysConfigs.push_back(static_cast<std::string>(ptrSystemConfig->GetValue()));
}
}
// Get the application config - but only when not specified over the app-control-config.
if (m_pathAppConfig.empty())
{
auto ptrAppConfig = parserSettings.GetRoot().GetDirect("Settings.AppConfig");
if (ptrAppConfig)
{
// Path available. Enable auto-update.
m_pathAppConfig = static_cast<std::string>(ptrAppConfig->GetValue());
m_bAutoSaveConfig = true;
}
}
}
catch (const sdv::toml::XTOMLParseException& rexcept)
{
if (!m_bSilent)
std::cerr << "ERROR: Failed to parse application settings: " << rexcept.what() << std::endl;
return false;
}
}
}
return true;
}
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
CAppControl& CAppControlService::GetAppControl()
{
return ::GetAppControl();
}
bool CAppControlService::EnableAppShutdownRequestAccess() const
{
return ::GetAppControl().IsMainApplication() || ::GetAppControl().IsIsolatedApplication();
}
#endif

View File

@@ -0,0 +1,344 @@
#ifndef APP_CONTROL_H
#define APP_CONTROL_H
#include <interfaces/app.h>
#include <support/component_impl.h>
#include <support/interface_ptr.h>
#include "../../global/tracefifo/trace_fifo.h"
///
/// @brief Application control class.
/// @details The application control class is responsible for the startup and shutdown of the system. Since the system is (re-)used
/// in many applications, the startup behavior can be determined by the provided configuration as string as argument to the
/// startup function.
/// The configuration uses the TOML format and is defined as follows:
/// @code
/// # Optional use of customized log handler
/// [LogHandler]
/// Class = "" # Component class name of a custom logger (optional)
/// Path = "" # Component module path of a custom logger (optional)
/// Tag = "" # Program tag to use instead of the name SDV_LOG_<pid>
/// Filter = "" # Lowest severity filter to use when logging (Trace, Debug, Info, Warning, Error, Fatal). Default severity
/// # level filter is Info (meaning Debug and Trace messages are not being stored).
/// ViewFilter = "" # Lowest severity filter to use when logging (Trace, Debug, Info, Warning, Error, Fatal). Default severity
/// # level filter is Error (meaning Debug, Trace, Info and Warning messages are not being shown).
///
/// # Application behavior definition
/// # Mode = "Standalone" (default) app->no RPC + core services + additional configurations allowed
/// # Mode = "External" app->RPC client only + local services + target service(s) --> connection information through listener
/// # Mode = "Isolated" app->RPC client only + local services + target service(s) --> connection information needed
/// # Mode = "Main" app->RPC server + core services --> access key needed
/// # Mode = "Essential" app->local services + additional configurations allowed
/// # Mode = "Maintenance" app->RPC client only + local services + maintenance service --> connection information needed + access key
/// # Instance = 1234
/// [Application]
/// Mode = "Main"
/// Instance = 1234 # Optional instance ID to be used with main and isolated applications. Has no influence on other
/// # applications. Default instance ID is 1000. The connection listener is using the instance ID to allow
/// # connections from an external application to the main application. Furthermore, the instance ID is
/// # used to locate the installation of SDV components. The location of the SDV components is relative to
/// # the executable (unless a target directory is supplied) added with the instance and the installations:
/// # &lt;exe_path&gt;/&lt;instance&gt;/&lt;installation&gt;
/// InstallDir = "./test" # Optional custom installation directory to be used with main and isolated applications. Has no
/// # influence on other applications. The default location for installations is the location of the
/// # executable. Specifying a different directory will change the location of installations to
/// # &lt;install_directory&gt;/&lt;instance&gt;/&lt;installation&gt;
/// # NOTE The directory of the core library and the directory of the running executable are always added
/// # to the system if they contain an installation manifest.
///
/// # Optional configuration that should be loaded (not for maintenance and isolated applications). This overrides the application
/// # config from the settings (only main application). Automatic saving the configuration is not supported.
/// Config = "abc.toml"
///
/// #Console output
/// [Console]
/// Report = "Silent" # Either "Silent", "Normal" or "Verbose" for no, normal or extensive messages.
///
/// # Search directories
/// @endcode
///
/// TODO: Add config ignore list (e.g. platform.toml, vehicle_ifc.toml and vehicle_abstract.toml).
/// Add dedicated config (rather than standard config) as startup param.
///
class CAppControl : public sdv::IInterfaceAccess, public sdv::app::IAppContext, public sdv::app::IAppControl,
public sdv::app::IAppOperation, public sdv::app::IAppShutdownRequest, public sdv::IAttributes
{
public:
/**
* @brief Constructor
*/
CAppControl();
/**
* @brief Destructor
*/
~CAppControl();
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::app::IAppOperation)
SDV_INTERFACE_ENTRY(sdv::app::IAppContext)
SDV_INTERFACE_ENTRY(sdv::app::IAppControl)
SDV_INTERFACE_ENTRY(sdv::app::IAppShutdownRequest)
END_SDV_INTERFACE_MAP()
/**
* @brief Return whether the current application is the main application.
* @return Returns 'true' when the current application is the main application; otherwise returns 'false'.
*/
bool IsMainApplication() const;
/**
* @brief Return whether the current application is an isolated application.
* @return Returns 'true' when the current application is an isolated application; otherwise returns 'false'.
*/
bool IsIsolatedApplication() const;
/**
* @brief Return whether the current application is a standalone application.
* @return Returns 'true' when the current application is a standalone application; otherwise returns 'false'.
*/
bool IsStandaloneApplication() const;
/**
* @brief Return whether the current application is an essential application.
* @return Returns 'true' when the current application is an essential application; otherwise returns 'false'.
*/
bool IsEssentialApplication() const;
/**
* @brief Return whether the current application is a maintenance application.
* @return Returns 'true' when the current application is a maintenance application; otherwise returns 'false'.
*/
bool IsMaintenanceApplication() const;
/**
* @brief Return whether the current application is an external application.
* @return Returns 'true' when the current application is an external application; otherwise returns 'false'.
*/
bool IsExternalApplication() const;
/**
* @brief Return the application context mode. Overload of sdv::app::IAppContext::GetContextType.
* @return The context mode.
*/
sdv::app::EAppContext GetContextType() const override;
/**
* @brief Return the core instance ID. Overload of sdv::app::IAppContext::GetContextType.
* @return The instance ID.
*/
uint32_t GetInstanceID() const override;
/**
* @brief Return the number of retries to establish a connection.
* @return Number of retries.
*/
uint32_t GetRetries() const override;
/**
* @brief Start the application. Overload of sdv::app::IAppControl::Startup.
* @details The core will prepare for an application start based on the provided configuration. Per default, the
* application will be in running mode after successful startup.
* @param[in] ssConfig String containing the configuration to be used by the core during startup. This configuration
* is optional. If not provided the application runs as standalone application without any RPC support.
* @param[in] pEventHandler Pointer to the event handler receiving application events. For the handler to receive
* events, the handler needs to expose the IAppEvent interface. This pointer is optionally and can be NULL.
* @return Returns 'true' on successful start-up and 'false' on failed startup.
*/
virtual bool Startup(/*in*/ const sdv::u8string& ssConfig, /*in*/ IInterfaceAccess* pEventHandler) override;
/**
* @brief Running loop until shutdown request is triggered.
*/
virtual void RunLoop() override;
/**
* @brief Initiate a shutdown of the application. Overload of sdv::app::IAppControl::Shutdown.
* @details The objects will be called to shutdown allowing them to clean up and gracefully shutdown. If, for some reason, the
* object cannot shut down (e.g. pointers are still in use or threads are not finalized), the object will be kept alive and the
* application state will stay in shutting-down-state. In that case the exception is called. A new call to the shutdown function
* using the force-flag might force a shutdown. Alternatively the application can wait until the application state changes to
* not-started.
* @remarks Application shutdown is only possible when all components are released.
* @param[in] bForce When set, forces an application shutdown. This might result in loss of data and should only be used as a
* last resort.
*/
virtual void Shutdown(/*in*/ bool bForce) override;
/**
* @brief Request shutdown. Overload of sdv::app::IAppShutdownRequest::RequestShutdown.
*/
virtual void RequestShutdown() override;
/**
* @brief Get the current operation state. This information is also supplied through the event handler function. Overload of
* sdv::app::IAppOperation::GetOperationState.
* @return Returns the operation state of the application.
*/
virtual sdv::app::EAppOperationState GetOperationState() const override;
/**
* @brief Get the current running instance.
* @details Get the instance. If not otherwise specified, the current instance depends on whether the application is running
* as main or isolated application, in which case the instance is 1000. In all other cases the instance is 0. A dedicated
* instance can be supplied through the app control.
* @return The instance number.
*/
uint32_t GetInstance() const;
/**
* @brief Switch from running mode to the configuration mode. Overload of sdv::app::IAppOperation::SetConfigMode.
*/
virtual void SetConfigMode() override;
/**
* @brief Switch from the configuration mode to the running mode. Overload of sdv::app::IAppOperation::SetRunningMode.
*/
virtual void SetRunningMode() override;
/**
* @brief Get a sequence with the available attribute names. Overload of sdv::IAttributes::GetNames.
* @return The sequence of attribute names.
*/
virtual sdv::sequence<sdv::u8string> GetNames() const override;
/**
* @brief Get the attribute value. Overload of sdv::IAttributes::Get.
* @param[in] ssAttribute Name of the attribute.
* @return The attribute value or an empty any-value if the attribute wasn't found or didn't have a value.
*/
virtual sdv::any_t Get(/*in*/ const sdv::u8string& ssAttribute) const override;
/**
* @brief Set the attribute value. Overload of sdv::IAttributes::Set.
* @param[in] ssAttribute Name of the attribute.
* @param[in] anyAttribute Attribute value to set.
* @return Returns 'true' when setting the attribute was successful or 'false' when the attribute was not found or the
* attribute is read-only or another error occurred.
*/
virtual bool Set(/*in*/ const sdv::u8string& ssAttribute, /*in*/ sdv::any_t anyAttribute) override;
/**
* @brief Get the attribute flags belonging to a certain attribute. Overload of sdv::IAttributes::GetFlags.
* @param[in] ssAttribute Name of the attribute.
* @return Returns the attribute flags (zero or more EAttributeFlags flags) or 0 when the attribute could not be found.
*/
virtual uint32_t GetFlags(/*in*/ const sdv::u8string& ssAttribute) const override;
/**
* @brief Get the installation directory of user components.
* @return The location of the user components. Only is valid when used in main and isolated applications.
*/
std::filesystem::path GetInstallDir() const;
/**
* @brief Disable the current auto update feature if enabled in the system settings.
*/
void DisableAutoConfigUpdate();
/**
* @brief Enable the current auto update feature if enabled in the system settings.
*/
void EnableAutoConfigUpdate();
/**
* @brief Trigger the config update if enabled in the system settings.
*/
void TriggerConfigUpdate();
/**
* @brief Should the console output be silent?
* @return Returns whether the console output is silent.
*/
bool IsConsoleSilent() const;
/**
* @brief Should the console output be verbose?
* @return Returns whether the verbose console output is activated.
*/
bool IsConsoleVerbose() const;
private:
/**
* @brief Set the operation state and broadcast the state through the event.
* @param[in] eState The new state the app control is in.
*/
void BroadcastOperationState(sdv::app::EAppOperationState eState);
/**
* @brief Process the application configuration before starting the system
* @param[in] rssConfig Reference to the configuration content.
* @return Returns 'true' when processing was successful; false when not.
*/
bool ProcessAppConfig(const sdv::u8string& rssConfig);
sdv::app::EAppContext m_eContextMode = sdv::app::EAppContext::no_context; ///< The application is running as...
sdv::app::EAppOperationState m_eState = sdv::app::EAppOperationState::not_started; ///< The current operation state.
sdv::app::IAppEvent* m_pEvent = nullptr; ///< Pointer to the app event interface.
std::string m_ssLoggerClass; ///< Class name of a logger service.
sdv::core::TModuleID m_tLoggerModuleID = 0; ///< ID of the logger module.
std::filesystem::path m_pathLoggerModule; ///< Module name of a custom logger.
std::string m_ssProgramTag; ///< Program tag to use when logging.
sdv::core::ELogSeverity m_eSeverityFilter = sdv::core::ELogSeverity::info; ///< Severity level filter while logging.
sdv::core::ELogSeverity m_eSeverityViewFilter = sdv::core::ELogSeverity::error; ///< Severity level filter while logging.
uint32_t m_uiRetries = 0u; ///< Number of retries to establish a connection.
uint32_t m_uiInstanceID = 0u; ///< Instance number.
std::filesystem::path m_pathInstallDir; ///< Location of user component installations.
std::filesystem::path m_pathRootDir; ///< Location of user component root directory.
std::vector<std::filesystem::path> m_vecSysConfigs; ///< The system configurations from the settings file.
std::filesystem::path m_pathAppConfig; ///< The application configuration from the settings file.
bool m_bAutoSaveConfig = false; ///< System setting for automatic saving of the configuration.
bool m_bEnableAutoSave = false; ///< When set and when enabled in the system settings, allows
///< the automatic saving of the configuration.
bool m_bRunLoop = false; ///< Used to detect end of running loop function.
bool m_bSilent = false; ///< When set, no console reporting takes place.
bool m_bVerbose = false; ///< When set, extensive console reporting takes place.
std::filesystem::path m_pathLockFile; ///< Lock file path name.
FILE* m_pLockFile = nullptr; ///< Lock file to test for other instances.
CTraceFifoStdBuffer m_fifoTraceStreamBuffer; ///< Trace stream buffer to redirect std::log, std::out and
///< std::err when running as service.
};
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
/**
* @brief App config service class.
*/
class CAppControlService : public sdv::CSdvObject
{
public:
CAppControlService() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY_MEMBER(sdv::app::IAppOperation, GetAppControl())
SDV_INTERFACE_ENTRY_MEMBER(sdv::IAttributes, GetAppControl())
SDV_INTERFACE_SET_SECTION_CONDITION(EnableAppShutdownRequestAccess(), 1)
SDV_INTERFACE_SECTION(1)
SDV_INTERFACE_ENTRY_MEMBER(sdv::app::IAppShutdownRequest, GetAppControl())
SDV_INTERFACE_DEFAULT_SECTION()
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_NAME("AppControlService")
DECLARE_OBJECT_SINGLETON()
/**
* @brief Get access to the application control.
* @return Returns the one global instance of the application config.
*/
static CAppControl& GetAppControl();
/**
* @brief When set, the application shutdown request interface access will be enabled.
* @return Returns 'true' when the access to the application configuration is granted; otherwise returns 'false'.
*/
bool EnableAppShutdownRequestAccess() const;
};
DEFINE_SDV_OBJECT_NO_EXPORT(CAppControlService)
#endif
#endif // !defined APP_CONTROL_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,470 @@
#ifndef INSTALL_PACKAGING_H
#define INSTALL_PACKAGING_H
#include <interfaces/config.h>
#include "installation_manifest.h"
#include <list>
#include <filesystem>
/// When enabled, support the read-only flag for files. By default this flag is not enabled due to limited support by the OS.
/// - The Windows OS provides native support for this flag
/// - The Linux allows installing additional libraries providing support, but this is not supported by all Linux versions.
/// - The Posix API doesn't provide support for the read-only flag.
#define COMPOSER_SUPPORT_READONLY_LINUX 0
#ifdef _WIN32
// Resolve conflict
#pragma push_macro("interface")
#undef interface
#pragma push_macro("GetObject")
#undef GetObject
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <WinSock2.h>
#include <Windows.h>
#include <objbase.h>
// Resolve conflict
#pragma pop_macro("GetObject")
#pragma pop_macro("interface")
#ifdef GetClassInfo
#undef GetClassInfo
#endif
/**
* @brief Convert the Windows FILETIME to the POSIX file time in micro-seconds.
* @details The Windows FILETIME structure counts with 100ns resolution and starts on the 1st of January 1601. The POSIX time
* counts with 1 second resolution and starts on the 1st of January 1970. V-API uses 1 micro-second resolution.
* @param[in] sWinTime The WIndows file time.
* @return The POSIX time in micro-seconds.
*/
inline uint64_t WindowsTimeToPosixTime(FILETIME sWinTime)
{
const uint64_t uiTicksPerMicroSecond = 10ull; // Windows counts in 100ns; POSIX in seconds
const uint64_t uiEpochDifference = 11644473600000000ull; // Windows starts counting on 1st Jan. 1601; POSIX on 1st Jan. 1970
return ((static_cast<uint64_t>(sWinTime.dwHighDateTime) << 32 | static_cast<uint64_t>(sWinTime.dwLowDateTime)) /
uiTicksPerMicroSecond) - uiEpochDifference;
}
/**
* @brief Convert the POSIX file time in micro-seconds to the Windows FILETIME.
* @details The Windows FILETIME structure counts with 100ns resolution and starts on the 1st of January 1601. The POSIX time
* counts with 1 second resolution and starts on the 1st of January 1970. V-API uses 1 micro-second resolution.
* @param[in] uiTime The POSIX time in micro-seconds.
* @return The Windows FILETIME.
*/
inline FILETIME PosixTimeToWindowsTime(uint64_t uiTime)
{
FILETIME sWinTime{};
const uint64_t uiTicksPerMicroSecond = 10ull; // Windows counts in 100ns; POSIX in seconds
const uint64_t uiEpochDifference = 11644473600000000ull; // Windows starts counting on 1st Jan. 1601; POSIX on 1st Jan. 1970
sWinTime.dwLowDateTime = static_cast<uint32_t>(((uiTime + uiEpochDifference) * uiTicksPerMicroSecond) & 0xffffffffull);
sWinTime.dwHighDateTime = static_cast<uint32_t>(((uiTime + uiEpochDifference) * uiTicksPerMicroSecond) >> 32ull);
return sWinTime;
}
#endif
/**
* @brief Installation packager class
*/
class CInstallComposer
{
public:
/**
* @brief Default constructor.
*/
CInstallComposer() = default;
/**
* @brief Destructor, finalizing the installation if not done so before.
*/
~CInstallComposer();
/**
* @brief Clear the current package to allow a new package composition.
*/
void Clear();
/**
* @brief Flags to use with the AddModule function.
*/
enum class EAddModuleFlags : uint32_t
{
wildcards = 0x00, ///< Add all modules that fit the modules path using wildcards to search for modules; could also
///< point to a directory. Wildcards are '*', '**' and '?'. The '*' will allow matching zero or
///< more characters until the next separator ('.' or '/'). A '**' allows matching of zero or
///< more characters across the directory separator '/'. A '?' allows matching one character.
///< Examples (using path: /dir1/dir2/dir3/dir4/file.txt)
///< - Pattern /dir1/**/*.txt - match
///< - Pattern /dir?/dir?/**/dir4 - match
///< - Pattern /**/*.txt - match
regex = 0x01, ///< Add all modules that fit the modules path using regular expression to search for modules;
///< could also point to a directory.
keep_structure = 0x10, ///< Maintain the directory structure when adding modules (requires a valid base directory).
};
/**
* @brief Installation update rules.
*/
enum class EUpdateRules
{
not_allowed, ///< Not allowed to update an existing installation.
update_when_new, ///< Allow to update when the installation is newer.
overwrite, ///< Always allow to update.
};
/**
* @brief Add a module to the installation.
* @throw Could throw a sdv::XSysExcept based exception.
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
* @details Add a module to the installation package.
* @param[in] rpathBasePath Reference to the base path to start searching for modules. Could be empty if the module path is
* absolute, in which case the directory structure cannot be stored at the target.
* @param[in] rssModulePath Reference to the string containing the module path to install. Depending on the options, this path
* could contain a pattern with wildcards or regular expressions. If the path is relative, the module base path must be valid.
* The module path is optional. If not supplied, the base path must be provided and all files within all subdirectories will
* be added.
* @param[in] rpathRelTargetDir Reference to the relative target directory the module(s) should be stored at.
* @param[in] uiFlags Zero or more flags from EAddModuleFlags.
* @return Returns the a vector of modules that were added. In case of using wildcards or regular expression, this could also
* be an empty list, which is not an error.
*/
std::vector<std::filesystem::path> AddModule(const std::filesystem::path& rpathBasePath, const std::string& rssModulePath,
const std::filesystem::path& rpathRelTargetDir = ".", uint32_t uiFlags = 0);
/**
* @brief Add a property value. This value will be added to the installation manifest.
* @details Add a property value, which will be included in the installation manifest. The properties "Description" and
* "Version" are used during package management.
* @param[in] rssName Name of the property. Spaces are allowed. Quotes are not allowed.
* @param[in] rssValue Property value.
*/
void AddProperty(const std::string& rssName, const std::string& rssValue);
/**
* @brief Compose the package in memory.
* @throw Could throw a sdv::XSysExcept based exception.
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
* automatically extracted from the component and added to the installation manifest.
* @param[in] rssInstallName Reference to the string containing the installation name.
* @return Returns a buffer to the package content.
*/
sdv::pointer<uint8_t> Compose(const std::string& rssInstallName) const;
/**
* @brief Compose the package to disk.
* @throw Could throw a sdv::XSysExcept based exception.
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
* automatically extracted from the component and added to the installation manifest.
* @param[in] rpathPackage Reference to the path receiving the package content. Any existing package will be overwritten.
* @param[in] rssInstallName Reference to the string containing the installation name.
* @return Returns whether the package composing was successful.
*/
bool Compose(const std::filesystem::path& rpathPackage, const std::string& rssInstallName) const;
/**
* @brief Compose the installation directly at the target directory (without package composing and extracting).
* @throw Could throw a sdv::XSysExcept based exception.
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
* automatically extracted from the component and added to the installation manifest.
* @param[in] rssInstallName Reference to the string containing the installation name.
* @param[in] rpathInstallDir Reference to the installation directory.
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
* @return Returns the installation manifest when the package extraction was successful; or an empty manifest when not.
*/
CInstallManifest ComposeDirect(const std::string& rssInstallName, const std::filesystem::path& rpathInstallDir,
EUpdateRules eUpdateRule = EUpdateRules::not_allowed) const;
/**
* @brief Compose an installation manifest.
* @throw Could throw a sdv::XSysExcept based exception.
* @attention This function is part of the composer. Since it is loading a module to retrieve the component manifest of the
* module, module code could be executed. it therefore imposes a security risk. Do not call this function in sdv_core!
* @details Compose a package from all the modules added through the AddModule function. Additionally add an installation
* manifest containing the module and component details. If the module is an SDV module, the component manifest will
* automatically extracted from the component and added to the installation manifest.
* @param[in] rssInstallName Reference to the string containing the installation name.
* @return Returns an initialized installation manifest if successful, or an empty manifest if not.
*/
CInstallManifest ComposeInstallManifest(const std::string& rssInstallName) const;
/**
* @brief Extract a package to an installation directory.
* @throw Could throw a sdv::XSysExcept based exception.
* @pre An installation directory must be available.
* @param[in] rptrPackage Reference to the pointer containing the package content.
* @param[in] rpathInstallDir Reference to the installation directory.
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
* @return Returns the installation manifest when the package extraction was successful; or an empty manifest when not.
*/
static CInstallManifest Extract(const sdv::pointer<uint8_t>& rptrPackage, const std::filesystem::path& rpathInstallDir,
EUpdateRules eUpdateRule = EUpdateRules::not_allowed);
/**
* @brief Extract a package to an installation directory.
* @throw Could throw a sdv::XSysExcept based exception.
* @pre An installation directory must be available.
* @param[in] rpathPackage Reference to the path of the package file.
* @param[in] rpathInstallDir Reference to the installation directory.
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
* @return Returns the installation manifest when the package extraction was successful; or an empty manifest when not.
*/
static CInstallManifest Extract(const std::filesystem::path& rpathPackage, const std::filesystem::path& rpathInstallDir,
EUpdateRules eUpdateRule = EUpdateRules::not_allowed);
/**
* @brief Remove an installation.
* @throw Could throw a sdv::XSysExcept based exception.
* @param[in] rssInstallName Reference to the string containing the installation name.
* @param[in] rpathInstallDir Reference to the installation directory.
* @return Returns the installation manifest of the removed installation when the removal was successful; or an empty manifest
* when not.
*/
static CInstallManifest Remove(const std::string& rssInstallName, const std::filesystem::path& rpathInstallDir);
/**
* @brief Verify the integrity of an installation package.
* @throw Could throw a sdv::XSysExcept based exception with information about the integrity violation.
* @param[in] rptrPackage Reference to the pointer containing the package content.
* @return Returns 'true' when the package extraction was successful; 'false' when not.
*/
static bool Verify(const sdv::pointer<uint8_t>& rptrPackage);
/**
* @brief Verify the integrity of an installation package.
* @throw Could throw a sdv::XSysExcept based exception with information about the integrity violation.
* @param[in] rpathPackage Reference to the path of the package file.
* @return Returns 'true' when the package extraction was successful; 'false' when not.
*/
static bool Verify(const std::filesystem::path& rpathPackage);
/**
* @brief Extract an installation manifest from a package.
* @throw Could throw a sdv::XSysExcept based exception.
* @param[in] rptrPackage Reference to the pointer containing the package content.
* @return Returns 'true' when the package extraction was successful; 'false' when not.
*/
static CInstallManifest ExtractInstallManifest(const sdv::pointer<uint8_t>& rptrPackage);
/**
* @brief Extract an installation manifest from a package.
* @throw Could throw a sdv::XSysExcept based exception.
* @param[in] rpathPackage Reference to the path of the package file.
* @return Returns 'true' when the package extraction was successful; 'false' when not.
*/
static CInstallManifest ExtractInstallManifest(const std::filesystem::path& rpathPackage);
private:
/**
* @brief File entry (modules and companion files).
*/
struct SFileEntry
{
std::filesystem::path pathSrcModule; ///! Source path to the module.
std::filesystem::path pathRelDir; ///< Relative directory within the installation.
};
/**
* @brief Compose and serialize a package header.
* @param[in, out] rptrPackage Reference to a pointer object receiving the serialized header. The pointer object will be
* adjusted in size to fit the header.
* @param[in] rmanifest Reference to the installation manifest to add.
* @return The checksum of data stored in the package pointer.
*/
static uint32_t SerializePackageHeader(sdv::pointer<uint8_t>& rptrPackage, const CInstallManifest& rmanifest);
/**
* @brief Serialize a module into a package BLOB.
* @param[in] uiChecksumInit The initial checksum to start calculating the BLOB checksum with.
* @param[in, out] rptrPackage Reference to a pointer object receiving the BLOB. The pointer object will be extended to fit
* the BLOB, which will be placed following the data stored in the pointer buffer. The existing data will not be touched.
* @param[in] rsFile Reference to the module entry to add as a BLOB.
* @return The checksum of data stored in the package pointer.
*/
static uint32_t SerializeModuleBLOB(uint32_t uiChecksumInit, sdv::pointer<uint8_t>& rptrPackage, const SFileEntry& rsFile);
/**
* @brief Serialize a final BLOB. This marks the end of the BLOBs within the package.
* @param[in] uiChecksumInit The initial checksum to start calculating the BLOB checksum with.
* @param[in, out] rptrPackage Reference to a pointer object receiving the BLOB. The pointer object will be extended to fit
* the BLOB, which will be placed following the data stored in the pointer buffer. The existing data will not be touched.
* @return The checksum of data stored in the package pointer.
*/
static uint32_t SerializeFinalBLOB(uint32_t uiChecksumInit, sdv::pointer<uint8_t>& rptrPackage);
/**
* @brief Serialize the package footer.
* @param[in] uiChecksum The checksum of the data to store in the footer.
* @param[in, out] rptrPackage Reference to a pointer object receiving the footer. The pointer object will be extended to fit
* the footer, which will be placed following the data stored in the pointer buffer. The existing data will not be touched.
*/
static void SerializePackageFooter(uint32_t uiChecksum, sdv::pointer<uint8_t>& rptrPackage);
/**
* @brief Extracts the header from the file.
* @details This function reads the header from the file from the current position and checks whether the content fits the
* checksum.
* @param[in, out] rfstream Reference to the opened package stream at the initial position.
* @param[out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete header. This checksum
* is used as input for the checksum of the following structures.
* @return Returns the content of the package header.
*/
static sdv::installation::SPackageHeader DeserializeHeader(std::ifstream& rfstream, uint32_t& ruiChecksum);
/**
* @brief Extracts the header from the file.
* @details This function reads the header from the file from the current position and checks whether the content fits the
* checksum.
* @param[in] rptrPackage Reference to the package containing the header.
* @param[out] rnOffset Reference to the variable receiving the offset location following the header.
* @param[out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete header. This checksum
* is used as input for the checksum of the following structures.
* @return Returns the content of the package header.
*/
static sdv::installation::SPackageHeader DeserializeHeader(const sdv::pointer<uint8_t>& rptrPackage, size_t& rnOffset,
uint32_t& ruiChecksum);
/**
* @brief Extracts the BLOB from the file.
* @details This function reads the BLOB from the file from the current position and checks whether the content fits the
* checksum.
* @param[in, out] rfstream Reference to the opened package stream at the current position.
* @param[in, out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete BLOB. This
* checksum is used as input for the checksum of the following structures.
* @return Returns the content of the package header.
*/
static sdv::installation::SPackageBLOB DeserializeBLOB(std::ifstream& rfstream, uint32_t& ruiChecksum);
/**
* @brief Extracts the BLOB from the file.
* @details This function reads the BLOB from the file from the current position and checks whether the content fits the
* checksum.
* @param[in] rptrPackage Reference to the package containing the BLOB.
* @param[in, out] rnOffset Reference to the variable containing the offset to the BLOB and receiving the offset following the
* BLOB.
* @param[in, out] ruiChecksum Reference to the variable receiving the checksum calculated over the complete BLOB. This
* checksum is used as input for the checksum of the following structures.
* @return Returns the content of the package header.
*/
static sdv::installation::SPackageBLOB DeserializeBLOB(const sdv::pointer<uint8_t>& rptrPackage, size_t& rnOffset, uint32_t& ruiChecksum);
/**
* @brief Check the final checksum using the package footer.
* @param[in, out] rfstream Reference to the opened package stream at the current position.
* @param[in] uiChecksum The calculated checksum of the package content to use for the calculation of the checksum of the
* footer.
*/
static void DeserializeFinalChecksum(std::ifstream& rfstream, uint32_t uiChecksum);
/**
* @brief Check the final checksum using the package footer.
* @param[in] rptrPackage Reference to the package containing the footer with the checksum.
* @param[in] nOffset The variable containing the offset to the footer.
* @param[in] uiChecksum The calculated checksum of the package content to use for the calculation of the checksum of the
* footer.
*/
static void DeserializeFinalChecksum(const sdv::pointer<uint8_t>& rptrPackage, size_t nOffset, uint32_t uiChecksum);
/**
* @brief Store the module from the supplied BLOB.
* @param[in] rsModuleBLOB Reference to the BLOB structure containing the module. If the BLOB doesn't contain the module,
* nothing occurs.
* @param[in] rpathLocation Reference to the path containing the location to store the module to.
*/
static void StoreModuleBLOB(const sdv::installation::SPackageBLOB& rsModuleBLOB, const std::filesystem::path& rpathLocation);
/**
* @brief Store the installation manifest at the provided location and set the creation time for the manifest file.
* @param[in] uiCreationTime The time of the package creation (will be used for the manifest file).
* @param[in] rpathLocation Reference to the path containing the location to store the manifest to.
* @param[in] rmanifest Reference to the manifest to store.
*/
static void StoreManifest(const std::filesystem::path& rpathLocation, const CInstallManifest& rmanifest, int64_t uiCreationTime);
/**
* @brief Set the file to read-only.
* @param[in] rpathFile Reference to the path of the file.
*/
static void SetReadOnly(const std::filesystem::path& rpathFile);
/**
* @brief Get the file read-only-state.
* @param[in] rpathFile Reference to the path of the file.
*/
static bool IsReadOnly(const std::filesystem::path& rpathFile);
/**
* @brief Set the file to executable.
* @remarks Not available for Windows.
* @param[in] rpathFile Reference to the path of the file.
*/
static void SetExecutable(const std::filesystem::path& rpathFile);
/**
* @brief Get the file to executable state.
* @remarks Not available for WIndows.
* @param[in] rpathFile Reference to the path of the file.
*/
static bool IsExecutable(const std::filesystem::path& rpathFile);
/**
* @brief Set the file creation time.
* @remarks Only available for Windows.
* @param[in] rpathFile Reference to the path of the file.
* @param[in] uiTimeMicrosec The unix epoch time in microseconds.
*/
static void SetCreateTime(const std::filesystem::path& rpathFile, uint64_t uiTimeMicrosec);
/**
* @brief Get the file creation time.
* @remarks Only available for Windows.
* @param[in] rpathFile Reference to the path of the file.
* @return Returns the unix epoch time in microseconds.
*/
static uint64_t GetCreateTime(const std::filesystem::path& rpathFile);
/**
* @brief Set the file change time.
* @param[in] rpathFile Reference to the path of the file.
* @param[in] uiTimeMicrosec The unix epoch time in microseconds.
*/
static void SetChangeTime(const std::filesystem::path& rpathFile, uint64_t uiTimeMicrosec);
/**
* @brief Get the file change time.
* @param[in] rpathFile Reference to the path of the file.
* @return Returns the unix epoch time in microseconds.
*/
static uint64_t GetChangeTime(const std::filesystem::path& rpathFile);
/**
* @brief Check whether thr installed version can be overwritten. This is the case when the version number of the current
* package is larger than the version number of the installed package.
* @remarks Returns 'true' if the current installation doesn't have an installation manifest.
* @remarks If there is no version stored in the installation manifest, the version number is considered 0.0.0.
* @param[in] rpathInstall Reference to the installation directory of the current installation. This directory must contain a
* "install_manifest.toml" file.
* @param[in] sVersionNew The version to use for comparison.
* @param[in] eUpdateRule Decide how to deal with updating an existing installation.
* @return Returns whether the existing installation can be updated (removed and installed again).
*/
static bool UpdateExistingInstallation(const std::filesystem::path& rpathInstall, sdv::installation::SPackageVersion sVersionNew, EUpdateRules eUpdateRule);
std::list<SFileEntry> m_lstFiles; ///< List of modules added to the package
std::map<std::string, std::string> m_mapProperties; ///< Property map.
};
#endif // !defined INSTALL_PACKAGING_H

View File

@@ -0,0 +1,478 @@
#include "installation_manifest.h"
#include "toml_parser/parser_toml.h"
#include <support/serdes.h>
#if defined _WIN32 && defined __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
#ifdef __unix__
#include <dlfcn.h>
#endif
/**
* @brief Read the module manifest from the binary.
* @param[in] rpathModule Reference to the module to read the manifest from.
* @return The manifest, if existing or an empty string when not.
*/
std::string ReadModuleManifest(const std::filesystem::path& rpathModule)
{
if (rpathModule.extension() != ".sdv") return {};
if (!std::filesystem::exists(rpathModule)) return {};
// Load the module
#ifdef _WIN32
sdv::core::TModuleID tModuleID = reinterpret_cast<sdv::core::TModuleID>(LoadLibrary(rpathModule.native().c_str()));
#elif defined __unix__
sdv::core::TModuleID tModuleID = reinterpret_cast<sdv::core::TModuleID>(dlopen(rpathModule.native().c_str(), RTLD_LAZY));
#else
#error OS not supported!
#endif
if (!tModuleID) return {};
// Check whether the module exposes the necessary functions
using TFNHasActiveObjects = bool();
using TFNGetModuleFactory = sdv::IInterfaceAccess*(uint32_t);
using TFNGetManifest = const char*();
#ifdef _WIN32
std::function<TFNGetModuleFactory> fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "GetModuleFactory"));
std::function<TFNHasActiveObjects> fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "HasActiveObjects"));
std::function<TFNGetManifest> fnGetManifest = reinterpret_cast<TFNGetManifest*>(GetProcAddress(reinterpret_cast<HMODULE>(tModuleID), "GetManifest"));
#elif defined __unix__
std::function<TFNGetModuleFactory> fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(dlsym(reinterpret_cast<void*>(tModuleID), "GetModuleFactory"));
std::function<TFNHasActiveObjects> fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(dlsym(reinterpret_cast<void*>(tModuleID), "HasActiveObjects"));
std::function<TFNGetManifest> fnGetManifest = reinterpret_cast<TFNGetManifest*>(dlsym(reinterpret_cast<void*>(tModuleID), "GetManifest"));
#else
#error OS not supported!
#endif
// Check for functions and the correct version
std::string ssManifest;
if (fnGetFactory && fnActiveObjects && fnGetManifest && fnGetManifest())
ssManifest = fnGetManifest();
// Release the library
#ifdef _WIN32
FreeLibrary(reinterpret_cast<HMODULE>(tModuleID));
#elif defined __unix__
dlclose(reinterpret_cast<void*>(tModuleID));
#else
#error OS not supported!
#endif
// Return the manifest
return ssManifest;
}
bool CInstallManifest::IsValid() const
{
// Must have an installation name to be valid.
return !m_ssInstallName.empty();
}
void CInstallManifest::Clear()
{
m_ssInstallName.clear();
m_vecModules.clear();
m_bBlockSystemObjects = false;
}
const std::string& CInstallManifest::InstallName() const
{
return m_ssInstallName;
}
sdv::installation::SPackageVersion CInstallManifest::Version() const
{
auto optProperty = Property("Version");
if (!optProperty) return {};
// A property string is composed of: major.minor.patch (numbers only; characters and whitespace are ignored).
sdv::installation::SPackageVersion sVersion{};
size_t nPos = optProperty->find('.');
sVersion.uiMajor = static_cast<uint32_t>(std::atoi(optProperty->substr(0, nPos).c_str()));
size_t nStart = nPos;
if (nStart != std::string::npos)
{
nStart++;
nPos = optProperty->find('.', nPos + 1);
sVersion.uiMinor = static_cast<uint32_t>(std::atoi(optProperty->substr(nStart, nPos).c_str()));
}
nStart = nPos;
if (nStart != std::string::npos)
{
nStart++;
sVersion.uiPatch = static_cast<uint32_t>(std::atoi(optProperty->substr(nStart).c_str()));
}
return sVersion;
}
const std::filesystem::path& CInstallManifest::InstallDir() const
{
return m_pathInstallDir;
}
bool CInstallManifest::Create(const std::string& rssInstallName)
{
Clear();
m_ssInstallName = rssInstallName;
return true;
}
bool CInstallManifest::Load(const std::filesystem::path& rpathInstallDir, bool bBlockSystemObjects /*= false*/)
{
if (rpathInstallDir.empty() || !std::filesystem::exists(rpathInstallDir) ||
!std::filesystem::is_directory(rpathInstallDir)) return {};
m_pathInstallDir = rpathInstallDir;
// Create the manifest file
std::ifstream fstream(rpathInstallDir / "install_manifest.toml");
if (!fstream.is_open()) return false;
// Read the manifest in memory
std::stringstream sstreamManifest;
sstreamManifest << fstream.rdbuf();
fstream.close();
return Read(sstreamManifest.str(), bBlockSystemObjects);
}
bool CInstallManifest::Save(const std::filesystem::path& rpathInstallDir) const
{
if (!IsValid() || rpathInstallDir.empty()) return false;
m_pathInstallDir = rpathInstallDir;
// Create the manifest file
std::ofstream fstream(rpathInstallDir / "install_manifest.toml", std::ios_base::out | std::ios_base::trunc);
if (!fstream.is_open()) return false;
std::string ssManifest = Write();
if (!ssManifest.empty()) fstream << ssManifest;
fstream.close();
return !ssManifest.empty();
}
bool CInstallManifest::Read(const std::string& rssManifest, bool bBlockSystemObjects /*= false*/)
{
Clear();
m_bBlockSystemObjects = bBlockSystemObjects;
// Parse the manifest
CParserTOML parser(rssManifest);
// Get the installation version - must be identical to the interface version
auto ptrInstallVersionNode = parser.GetRoot().GetDirect("Installation.Version");
if (!ptrInstallVersionNode || ptrInstallVersionNode->GetValue() != SDVFrameworkInterfaceVersion) return false;
// Get the installation name
auto ptrInstallNameNode = parser.GetRoot().GetDirect("Installation.Name");
if (!ptrInstallNameNode) return false;
m_ssInstallName = static_cast<std::string>(ptrInstallNameNode->GetValue());
if (m_ssInstallName.empty()) return false;
// Get installation properties. The properties are optional
auto ptrProperties = parser.GetRoot().GetDirect("Properties");
std::shared_ptr<CTable> ptrPropertyTable;
if (ptrProperties) ptrPropertyTable = ptrProperties->GetTable();
if (ptrPropertyTable)
{
for (uint32_t uiIndex = 0; uiIndex < ptrPropertyTable->GetCount(); uiIndex++)
{
auto ptrProperty = ptrPropertyTable->Get(uiIndex);
if (ptrProperty)
m_mapProperties[ptrProperty->GetName()] = static_cast<std::string>(ptrProperty->GetValue());
}
}
// Build the module list
auto ptrModulesNode = parser.GetRoot().GetDirect("Module");
if (!ptrModulesNode) return true; // No modules in the manifest
auto ptrModuleArrayNode = ptrModulesNode->GetArray();
if (!ptrModuleArrayNode) return false; // Must be array
for (uint32_t uiModuleIndex = 0; uiModuleIndex < ptrModuleArrayNode->GetCount(); uiModuleIndex++)
{
// Get the module
auto ptrModule = ptrModuleArrayNode->Get(uiModuleIndex);
if (!ptrModule) continue;
// Get the module path
auto ptrModulePath = ptrModule->GetDirect("Path");
if (!ptrModulePath) continue;
std::filesystem::path pathModule = static_cast<std::string>(ptrModulePath->GetValue());
std::string ssModuleManifest;
// Get the component list (if available)
auto ptrModuleComponents = ptrModule->GetDirect("Component");
if (ptrModuleComponents)
{
// The module manifest contains the TOML text of the component array
auto ptrModuleComponentArray = ptrModuleComponents->GetArray();
if (ptrModuleComponentArray)
ssModuleManifest = ptrModuleComponents->CreateTOMLText();
}
// Add the module
m_vecModules.push_back(SModule(pathModule, ssModuleManifest,
m_bBlockSystemObjects));
}
return true;
}
std::string CInstallManifest::Write() const
{
if (!IsValid()) return {};
std::stringstream sstream;
// Add the installation section
sstream << "[Installation]" << std::endl << "Version = " << SDVFrameworkSubbuildVersion << std::endl << "Name = \"" <<
m_ssInstallName << "\"" << std::endl << std::endl;
// Add the properties section (if there are any properties)
if (!m_mapProperties.empty())
{
sstream << "[Properties]" << std::endl;
for (const auto& rvtProperty : m_mapProperties)
{
if (NeedQuotedName(rvtProperty.first))
sstream << "\"" << rvtProperty.first << "\"";
else
sstream << rvtProperty.first;
sstream << " = \"" << rvtProperty.second << "\"" << std::endl;
}
sstream << std::endl;
}
// Add the modules
for (const SModule& rsEntry : m_vecModules)
{
sstream << "[[Module]]" << std::endl << "Path=\"" << rsEntry.pathRelModule.generic_u8string() << "\"" << std::endl;
// Read the module manifest
CParserTOML parser(rsEntry.ssManifest);
// Add the module manifest as part of the installation manifest.
sstream << parser.CreateTOMLText("Module") << std::endl;
}
return sstream.str();
}
bool CInstallManifest::AddModule(const std::filesystem::path& rpathModulePath,
const std::filesystem::path& rpathRelTargetDir /*= "."*/)
{
if (!IsValid()) return false;
// Check for the existence of the module.
if (!std::filesystem::exists(rpathModulePath)) return false;
if (!std::filesystem::is_regular_file(rpathModulePath)) return false;
// Check for a relative path
if (!rpathRelTargetDir.is_relative()) return false;
if (RefersToRelativeParent(rpathRelTargetDir)) return false;
// Read the manifest...
std::string ssManifest = ReadModuleManifest(rpathModulePath);
// Read the component manifest if existing
std::string ssComponentsManifest;
if (!ssManifest.empty())
{
// Check the interface version for compatibility
CParserTOML parser(ssManifest);
auto ptrInterfaceNode = parser.GetRoot().GetDirect("Interface.Version");
if (!ptrInterfaceNode) return false;
if (ptrInterfaceNode->GetValue() != SDVFrameworkInterfaceVersion) return false;
auto ptrComponentsNode = parser.GetRoot().GetDirect("Component");
if (!ptrComponentsNode) return true; // No component available in the manifest
if (!ptrComponentsNode->GetArray()) return false;
ssComponentsManifest = ptrComponentsNode->CreateTOMLText();
}
// Store path and component
std::filesystem::path pathRelModule = (rpathRelTargetDir / rpathModulePath.filename()).lexically_normal();
m_vecModules.push_back(SModule(pathRelModule,
ssComponentsManifest, m_bBlockSystemObjects));
return true;
}
std::filesystem::path CInstallManifest::FindModule(const std::filesystem::path& rpathRelModule) const
{
if (!IsValid() || m_pathInstallDir.empty())
return {};
// Search for the correct module
auto itModule = std::find_if(
m_vecModules.begin(), m_vecModules.end(), [&](const SModule& rsEntry) { return rsEntry.pathRelModule == rpathRelModule; });
if (itModule != m_vecModules.end())
return m_pathInstallDir / rpathRelModule;
return {};
}
std::string CInstallManifest::FindModuleManifest(const std::filesystem::path& rpathRelModule) const
{
if (!IsValid() || m_pathInstallDir.empty())
return {};
// Search for the correct module
auto itModule = std::find_if(
m_vecModules.begin(), m_vecModules.end(), [&](const SModule& rsEntry) { return rsEntry.pathRelModule == rpathRelModule; });
if (itModule != m_vecModules.end())
return itModule->ssManifest;
return {};
}
std::optional<CInstallManifest::SComponent> CInstallManifest::FindComponentByClass(const std::string& rssClass) const
{
// Search for the correct module
SComponent sRet{};
auto itModule = std::find_if(m_vecModules.begin(), m_vecModules.end(), [&](const SModule& rsEntry)
{
return std::find_if(rsEntry.vecComponents.begin(), rsEntry.vecComponents.end(),
[&](const SComponent& sComponent)
{
// Note, use the class, alias and the default object name for searching...
if (sComponent.ssClassName == rssClass ||
std::find(sComponent.seqAliases.begin(), sComponent.seqAliases.end(), rssClass) !=
sComponent.seqAliases.end())
{
sRet = sComponent;
return true;
}
return false;
}) != rsEntry.vecComponents.end();
});
if (itModule != m_vecModules.end()) return sRet;
return {};
}
std::vector<CInstallManifest::SComponent> CInstallManifest::ComponentList() const
{
std::vector<SComponent> vecComponents;
for (const auto& rsModule : m_vecModules)
{
for (const auto& rsComponent : rsModule.vecComponents)
vecComponents.push_back(rsComponent);
}
return vecComponents;
}
std::vector<std::filesystem::path> CInstallManifest::ModuleList() const
{
std::vector<std::filesystem::path> vecModules;
for (const auto& rsModule : m_vecModules)
vecModules.push_back(rsModule.pathRelModule);
return vecModules;
}
std::vector<std::pair<std::string, std::string>> CInstallManifest::PropertyList() const
{
return std::vector<std::pair<std::string, std::string>>(m_mapProperties.begin(), m_mapProperties.end());
}
void CInstallManifest::Property(const std::string& rssName, const std::string& rssValue)
{
// Check for a quote in the name... if existing then do not allow insertion.
if (rssName.find_first_of("\"'") != std::string::npos)
return;
m_mapProperties[rssName] = rssValue;
}
std::optional<std::string> CInstallManifest::Property(const std::string& rssName) const
{
auto itProperty = m_mapProperties.find(rssName);
if (itProperty == m_mapProperties.end())
return {};
return itProperty->second;
}
bool CInstallManifest::NeedQuotedName(const std::string& rssName)
{
for (char c : rssName)
{
if (!std::isalnum(c) && c != '_' && c != '-')
return true;
}
return false;
}
CInstallManifest::SModule::SModule(const std::filesystem::path& rpathRelModule, const std::string& rssManifest,
bool bBlockSystemObjects) : pathRelModule(rpathRelModule), ssManifest(rssManifest)
{
// Parse the manifest and extract information from them...
CParserTOML parser(rssManifest);
auto ptrComponents = parser.GetRoot().GetDirect("Component");
if (!ptrComponents) return; // No objects...
auto ptrComponentArray = ptrComponents->GetArray();
if (!ptrComponentArray) return; // No objects...
for (uint32_t uiIndex = 0; uiIndex < ptrComponentArray->GetCount(); uiIndex++)
{
auto ptrComponent = ptrComponentArray->Get(uiIndex);
if (!ptrComponent) continue;
// Fill in the component structure
SComponent sComponent{};
//sComponent.pathModule = rpathModule;
sComponent.pathRelModule = rpathRelModule;
sComponent.ssManifest = ptrComponent->CreateTOMLText("Component");
auto ptrClassName = ptrComponent->GetDirect("Class");
if (!ptrClassName) continue;
sComponent.ssClassName = static_cast<std::string>(ptrClassName->GetValue());
auto ptrAliases = ptrComponent->GetDirect("Aliases");
if (ptrAliases)
{
auto ptrAliasesArray = ptrAliases->GetArray();
for (uint32_t uiAliasIndex = 0; ptrAliasesArray && uiAliasIndex < ptrAliasesArray->GetCount(); uiAliasIndex++)
{
auto ptrClassAlias = ptrAliasesArray->Get(uiAliasIndex);
if (ptrClassAlias)
sComponent.seqAliases.push_back(static_cast<sdv::u8string>(ptrClassAlias->GetValue()));
}
}
auto ptrDefaultName = ptrComponent->GetDirect("DefaultName");
if (ptrDefaultName) sComponent.ssDefaultObjectName = static_cast<std::string>(ptrDefaultName->GetValue());
else sComponent.ssDefaultObjectName = sComponent.ssClassName;
auto ptrType = ptrComponent->GetDirect("Type");
if (!ptrType) continue;
std::string ssType = static_cast<std::string>(ptrType->GetValue());
if (ssType == "System") sComponent.eType = sdv::EObjectType::SystemObject;
else if (ssType == "Device") sComponent.eType = sdv::EObjectType::Device;
else if (ssType == "BasicService") sComponent.eType = sdv::EObjectType::BasicService;
else if (ssType == "ComplexService") sComponent.eType = sdv::EObjectType::ComplexService;
else if (ssType == "App") sComponent.eType = sdv::EObjectType::Application;
else if (ssType == "Proxy") sComponent.eType = sdv::EObjectType::Proxy;
else if (ssType == "Stub") sComponent.eType = sdv::EObjectType::Stub;
else if (ssType == "Utility") sComponent.eType = sdv::EObjectType::Utility;
else continue;
if (bBlockSystemObjects && sComponent.eType == sdv::EObjectType::SystemObject) continue;
auto ptrSingleton = ptrComponent->GetDirect("Singleton");
if (ptrSingleton && static_cast<bool>(ptrSingleton->GetValue()))
sComponent.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
auto ptrDependencies = ptrComponent->GetDirect("Dependencies");
if (ptrDependencies)
{
auto ptrDependencyArray = ptrDependencies->GetArray();
for (uint32_t uiDependencyIndex = 0; ptrDependencyArray && uiDependencyIndex < ptrDependencyArray->GetCount();
uiDependencyIndex++)
{
auto ptrDependsOn = ptrDependencyArray->Get(uiDependencyIndex);
if (ptrDependsOn)
sComponent.seqDependencies.push_back(static_cast<sdv::u8string>(ptrDependsOn->GetValue()));
}
}
vecComponents.push_back(sComponent);
}
}
#if defined _WIN32 && defined __GNUC__
#pragma GCC diagnostic pop
#endif

View File

@@ -0,0 +1,402 @@
#ifndef INSTALL_MANIFEST_H
#define INSTALL_MANIFEST_H
#include <filesystem>
#include <string>
#include <vector>
#include <optional>
#include <interfaces/core.h>
#include <interfaces/config.h>
#include <map>
#include <cstdlib>
/**
* @brief Check whether a relative path is directing to a parent of the base path.
* @details Detect whether a relative path joined to the base path is referring to a parent of the base path. For example, the
* base path is "/home/user/project" and the relative path is "..". Both joined together would make "/home/user", which is a parent
* of the base path.
* @param[in] rpathBase Reference to the base path.
* @param[in] rpathRelative Reference to the relative path following the base.
* @return Returns whether the relative path is a parent of the base (not the base or a child of the base).
*/
inline bool IsParentPath(const std::filesystem::path& rpathBase, const std::filesystem::path& rpathRelative)
{
auto pathAbsBase = std::filesystem::weakly_canonical(rpathBase);
auto pathAbsPotentialParent = std::filesystem::weakly_canonical(rpathBase / rpathRelative);
// Check if pathAbsPotentialParent supersedes pathAbsBase. If not, it points to or derives from a parent.
auto itBase = pathAbsBase.begin();
auto itPotentialParent = pathAbsPotentialParent.begin();
while (itBase != pathAbsBase.end())
{
// If the potential path has finished, it is pointing to a parent.
if (itPotentialParent == pathAbsPotentialParent.end()) return true;
// If the path-parts are not equal, the potential parent part is really deriving from a parent.
if (*itBase != *itPotentialParent) return true;
// Check next
itBase++;
itPotentialParent++;
}
// Even if the potential path might be still have more parts, they derive from the base path.
return false;
}
/**
* @brief Check whether the relative path directs to a parent path.
* @details Detect whether a relative path directs to a parent path. For example with "./dir1/dir2/../dir3"; this is not the case,
* but with "./dir1/dir2/../../../dir3" this is the case.
* @param[in] rpathRelative Reference to the relative path to check for.
* @return Returns whether the relative path refers to the
*/
inline bool RefersToRelativeParent(const std::filesystem::path& rpathRelative)
{
int iDepth = 0;
for (const auto& pathPart : rpathRelative)
{
if (pathPart == "..")
--iDepth;
else if (pathPart != "." && pathPart != "")
++iDepth;
if (iDepth < 0) return true; // Not allowed to be negative.
}
return false;
}
/**
* @brief Class managing the installation manifest.
* @details The installation manifest is a TOML file with the name "install_manifest.toml" and the following format:
* @code
* [Installation]
* Version = 100 # Installation manifest version.
* Name = "Duck" # Name of the installation. This typically is identical to the installation directory name.
*
* [Properties]
* Product = "Wild goose components" # Product name
* Description = "Mallard installation" # Description
* Author = "Nils Holgerson" # Author
* Address = """Vildgåsvägen 7
* Skanör med Falsterbo
* Skåne län, 123 45
* Sverige""" # Address
* Copyrights = "(C) 2025 Wild goose" # Copyrights
* Version = "0.1.2.3" # Package version
*
* [[Module]]
* Path = "mallard.sdv # Relative path to the module
*
* [[Module.Component]] # Component manifest
* Class = "Mallard class" # The name of the class
* Aliases = ["Duck", "Pont duck"] # Optional list of aliases
* DefaultName = "Duck" # Optional default name for the class instance
* Type = "Complex service" # Component type (Device, BasicService, ComplexService, App, Proxy, Stub, Utility)
* Singleton = false # Optional singleton flag
* Dependencies = ["Bird", "Animal"] # Optional list of dependencies
*
* [[Module.Component]] # Another component manifest
* #...
*
* [[Module]] # Another module
* Path = "large/greylag_goose.sdv # Relative path to the module
* @endcode
* @remarks The installation directory path is used to create relative paths to the modules. It is not stored in the manifest
* itself allowing the manifest to be copied from one location to another as long as the relative path to the modules is maintained
* (meaning copying the modules along with the manifest).
*/
class CInstallManifest
{
public:
/**
* @brief Manifest information belonging to the component.
*/
struct SComponent
{
std::filesystem::path pathRelModule; ///< Relative module path (relative to the installation directory).
std::string ssManifest; ///< Component manifest.
std::string ssClassName; ///< String representing the class name.
sdv::sequence<sdv::u8string> seqAliases; ///< Sequence containing zero or more class name aliases.
std::string ssDefaultObjectName; ///< The default object name.
sdv::EObjectType eType; ///< Type of object.
uint32_t uiFlags; ///< Zero or more object flags from EObjectFlags.
sdv::sequence<sdv::u8string> seqDependencies; ///< This component depends on...
};
/**
* @brief Default constructor.
*/
CInstallManifest() = default;
/**
* @brief Is this a valid installation manifest (installation directory path is known and a name is given)?
* @return Returns whether the installation manifest is valid.
*/
bool IsValid() const;
/**
* @brief Clear the current manifest to start a new manifest.
*/
void Clear();
/**
* @brief Get the installation name.
* @return Reference to the string holding the installation name.
*/
const std::string& InstallName() const;
/**
* @brief Get the package version from the version property.
* @remarks The version property can hold additional information, which is not available through this function.
* @return Structure containing the version information.
*/
sdv::installation::SPackageVersion Version() const;
/**
* @brief Get the installation directory path.
* @return Reference to the path to the installation.
*/
const std::filesystem::path& InstallDir() const;
/**
* @brief Create a new manifest.
* @param[in] rssInstallName Reference to the string containing the installaltion name.
* @return Returns whether manifest creation was successful.
*/
bool Create(const std::string& rssInstallName);
/**
* @brief Load a manifest TOML file.
* @param[in] rpathInstallDir Reference to the installation directory.
* @param[in] bBlockSystemObjects When set, system objects are not stored in the repository.
* @return Returns whether loading the TOML file was successful.
*/
bool Load(const std::filesystem::path& rpathInstallDir, bool bBlockSystemObjects = false);
/**
* @brief Save a manifest TOML file.
* @param[in] rpathInstallDir Reference to the installation directory.
* @return Returns whether saving the TOML file was successful.
*/
bool Save(const std::filesystem::path& rpathInstallDir) const;
/**
* @brief Read a manifest TOML string.
* @param[in] rssManifest Reference to the string containing the manifest.
* @param[in] bBlockSystemObjects When set, system objects are not stored in the repository.
* @return Returns whether reading the TOML string was successful.
*/
bool Read(const std::string& rssManifest, bool bBlockSystemObjects = false);
/**
* @brief Write a manifest TOML string.
* @return The manifest TOML string when successful or an empty string when not.
*/
std::string Write() const;
/**
* @brief Add a module to the installation manifest (if the module contains components).
* @attention This function should not be called by the core application, since it imposes a security risk!
* @param[in] rpathModulePath Reference to the module path.
* @param[in] rpathRelTargetDir The relative target directory the module should be stored at.
* @return Returns 'true' when successful; 'false' when not.
*/
bool AddModule(const std::filesystem::path& rpathModulePath, const std::filesystem::path& rpathRelTargetDir = ".");
/**
* @brief Find the module stored in the installation manifest.
* @pre Only successful for manifests having an installation directory.
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
* @return Returns the full path if the module was found or an empty path when not.
*/
std::filesystem::path FindModule(const std::filesystem::path& rpathRelModule) const;
/**
* @brief Find the module manifest.
* @pre Only successful for manifests having an installation directory.
* @param[in] rpathRelModule Reference to the path containing the relative path to a module.
* @return Returns the string containing the module manifest.
*/
std::string FindModuleManifest(const std::filesystem::path& rpathRelModule) const;
/**
* @brief Find the component stored in this installation manifest.
* @param[in] rssClass Reference to the class name of the component.
* @return The component manifest information.
*/
std::optional<SComponent> FindComponentByClass(const std::string& rssClass) const;
/**
* @brief Get a vector of all components stored in this installation manifest.
* @return The component manifest vector.
*/
std::vector<SComponent> ComponentList() const;
/**
* @brief Get the module list.
* @return Returns a vector with paths to all modules relative to the installation directory.
*/
std::vector<std::filesystem::path> ModuleList() const;
/**
* @brief Get the property list.
* @return Returns a vector with properties and the corresponding values.
*/
std::vector<std::pair<std::string, std::string>> PropertyList() const;
/**
* @brief Set a property value.
* @details Set a property value, which will be included in the installation manifest. The properties "Description" and
* "Version" are used during package management.
* @remarks Adding a property with the same name as a previously added property will replace the previous property value.
* @param[in] rssName Name of the property. Spaces are allowed. Quotes are not allowed.
* @param[in] rssValue Property value.
*/
void Property(const std::string& rssName, const std::string& rssValue);
/**
* @brief Get a property value.
* @param[in] rssName Name of the property. Spaces are allowed. Quotes are not allowed.
* @return Returns the property value if existing.
*/
std::optional<std::string> Property(const std::string& rssName) const;
private:
/**
* @brief Checks the name for adherence to the bare key rule of TOML. If a bare key is not possible, a quoted key is required.
* @remarks TOML defines for bare keys that they can only be composed of ASCII letters, ASCII digits, underscores and dashes.
* @param[in] rssName Name to check for.
* @return Returns whether quotes are required.
*/
static bool NeedQuotedName(const std::string& rssName);
/**
* @brief Manifest information belonging to the module.
*/
struct SModule
{
/**
* @brief Constructor
* @param[in] rpathRelModule Reference to the relative module path.
* @param[in] rssManifest Reference to the manifest file.
* @param[in] bBlockSystemObjects Set when to block system objects.
*/
SModule(const std::filesystem::path& rpathRelModule, const std::string& rssManifest, bool bBlockSystemObjects);
std::filesystem::path pathRelModule; ///< Relative module path (relative to the installation directory).
std::string ssManifest; ///< Manifest containing the components.
std::vector<SComponent> vecComponents; ///< Vector with contained components
};
std::string m_ssInstallName; ///< Installation name.
mutable std::filesystem::path m_pathInstallDir; ///< Installation directory when install manifest was
///< loaded or saved.
bool m_bBlockSystemObjects = false; ///< When set, do not store system objects.
std::vector<SModule> m_vecModules; ///< Vector containing the modules.
std::map<std::string, std::string> m_mapProperties; ///< Property map.
};
/**
* @brief Interpret a version number as string.
* @details A version string is composed of: major.minor.patch (numbers only; characters and whitespace are ignored).
* @param rssVersion Reference to the version string.
* @return The interpreted version.
*/
inline sdv::installation::SPackageVersion InterpretVersionString(const std::string& rssVersion)
{
//
sdv::installation::SPackageVersion sVersion{};
size_t nPos = rssVersion.find('.');
sVersion.uiMajor = static_cast<uint32_t>(std::atoi(rssVersion.substr(0, nPos).c_str()));
size_t nStart = nPos;
if (nStart != std::string::npos)
{
nStart++;
nPos = rssVersion.find('.', nPos + 1);
sVersion.uiMinor = static_cast<uint32_t>(std::atoi(rssVersion.substr(nStart, nPos).c_str()));
}
nStart = nPos;
if (nStart != std::string::npos)
{
nStart++;
sVersion.uiPatch = static_cast<uint32_t>(std::atoi(rssVersion.substr(nStart).c_str()));
}
return sVersion;
}
/**
* @brief Equality operator for package version.
* @param[in] sFirst First package version used for the comparison.
* @param[in] sSecond Second package version used for the comparison.
* @return The result of the comparison.
*/
constexpr inline bool operator==(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
{
return sFirst.uiMajor == sSecond.uiMajor && sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch == sSecond.uiPatch;
}
/**
* @brief Equal or larger than operator for package version.
* @param[in] sFirst First package version used for the comparison.
* @param[in] sSecond Second package version used for the comparison.
* @return The result of the comparison.
*/
constexpr inline bool operator>=(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
{
return sFirst.uiMajor > sSecond.uiMajor
|| (sFirst.uiMajor == sSecond.uiMajor
&& (sFirst.uiMinor > sSecond.uiMinor ||
(sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch >= sSecond.uiPatch)));
}
/**
* @brief Larger than operator for package version.
* @param[in] sFirst First package version used for the comparison.
* @param[in] sSecond Second package version used for the comparison.
* @return The result of the comparison.
*/
constexpr inline bool operator>(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
{
return sFirst.uiMajor > sSecond.uiMajor
|| (sFirst.uiMajor == sSecond.uiMajor
&& (sFirst.uiMinor > sSecond.uiMinor || (sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch > sSecond.uiPatch)));
}
/**
* @brief Inequality operator for package version.
* @param[in] sFirst First package version used for the comparison.
* @param[in] sSecond Second package version used for the comparison.
* @return The result of the comparison.
*/
constexpr inline bool operator!=(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
{
return sFirst.uiMajor != sSecond.uiMajor || sFirst.uiMinor != sSecond.uiMinor || sFirst.uiPatch != sSecond.uiPatch;
}
/**
* @brief Equality operator for package version.
* @param[in] sFirst First package version used for the comparison.
* @param[in] sSecond Second package version used for the comparison.
* @return The result of the comparison.
*/
constexpr inline bool operator<(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
{
return sFirst.uiMajor < sSecond.uiMajor
|| (sFirst.uiMajor == sSecond.uiMajor
&& (sFirst.uiMinor < sSecond.uiMinor || (sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch < sSecond.uiPatch)));
}
/**
* @brief Equality operator for package version.
* @param[in] sFirst First package version used for the comparison.
* @param[in] sSecond Second package version used for the comparison.
* @return The result of the comparison.
*/
constexpr inline bool operator<=(sdv::installation::SPackageVersion sFirst, sdv::installation::SPackageVersion sSecond)
{
return sFirst.uiMajor < sSecond.uiMajor
|| (sFirst.uiMajor == sSecond.uiMajor
&& (sFirst.uiMinor < sSecond.uiMinor || (sFirst.uiMinor == sSecond.uiMinor && sFirst.uiPatch <= sSecond.uiPatch)));
}
#endif // !defined INSTALL_MANIFEST_H

View File

@@ -0,0 +1,52 @@
#include "iso_monitor.h"
#include "sdv_core.h"
#include "app_control.h"
CIsoMonitor::CIsoMonitor(sdv::IInterfaceAccess* pObject) :
m_ptrObject(pObject), m_pObjectControl(m_ptrObject.GetInterface<sdv::IObjectControl>())
{}
CIsoMonitor::~CIsoMonitor()
{
GetAppControl().RequestShutdown();
}
void CIsoMonitor::Initialize(/*in*/ const sdv::u8string& ssObjectConfig)
{
if (m_pObjectControl)
{
m_pObjectControl->Initialize(ssObjectConfig);
m_eObjectStatus = m_pObjectControl->GetStatus();
}
else
m_eObjectStatus = sdv::EObjectStatus::initialized;
}
sdv::EObjectStatus CIsoMonitor::GetStatus() const
{
if (m_pObjectControl) return m_pObjectControl->GetStatus();
return m_eObjectStatus;
}
void CIsoMonitor::SetOperationMode(/*in*/ sdv::EOperationMode eMode)
{
if (m_pObjectControl) m_pObjectControl->SetOperationMode(eMode);
}
void CIsoMonitor::Shutdown()
{
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
if (m_pObjectControl)
{
m_pObjectControl->Shutdown();
m_eObjectStatus = m_pObjectControl->GetStatus();
}
m_eObjectStatus = sdv::EObjectStatus::destruction_pending;
GetAppControl().RequestShutdown();
m_pObjectControl = nullptr;
}
sdv::IInterfaceAccess* CIsoMonitor::GetContainedObject()
{
return m_ptrObject;
}

View File

@@ -0,0 +1,69 @@
#ifndef ISOLATION_OBJECT_MONITOR_H
#define ISOLATION_OBJECT_MONITOR_H
#include <support/component_impl.h>
/**
* @brief Isolation object monitor. If shutdown is called, the application leaves the running loop.
* @remarks Only valid when running in an isolated application.
*/
class CIsoMonitor : public sdv::IInterfaceAccess, public sdv::IObjectControl
{
public:
/**
* @brief Constructor
* @param[in] pObject Pointer to the object to monitor
*/
CIsoMonitor(sdv::IInterfaceAccess* pObject);
/**
* @brief Destructor
*/
~CIsoMonitor();
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
SDV_INTERFACE_CHAIN_MEMBER(m_ptrObject)
END_SDV_INTERFACE_MAP()
/**
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
* @param[in] ssObjectConfig Optional configuration string.
*/
virtual void Initialize(/*in*/ const sdv::u8string& ssObjectConfig) override;
/**
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
* @return Return the current status of the object.
*/
virtual sdv::EObjectStatus GetStatus() const override;
/**
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
* @param[in] eMode The operation mode, the component should run in.
*/
virtual void SetOperationMode(/*in*/ sdv::EOperationMode eMode) override;
/**
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
* @attention Implement calls to other SDV objects here as this is no longer considered safe during the destructor of the object!
* After a call to shutdown any threads/callbacks/etc that could call other SDV objects need to have been stopped.
* The SDV object itself is to remain in a state where it can respond to calls to its interfaces as other objects may still call it during the shutdown sequence!
* Any subsequent call to GetStatus should return EObjectStatus::destruction_pending
*/
virtual void Shutdown() override;
/**
* @brief Get the contained object from the isolation monitor.
* @return The contained object.
*/
sdv::IInterfaceAccess* GetContainedObject();
private:
sdv::TInterfaceAccessPtr m_ptrObject; ///< Smart pointer to the object.
sdv::IObjectControl* m_pObjectControl = nullptr; ///< Pointer to the object control of the application
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status (in case there is no object control).
};
#endif // !defined ISOLATION_OBJECT_MONITOR_H

View File

@@ -0,0 +1,240 @@
#ifndef LOCAL_SHUTDOWN_REQUEST_H
#define LOCAL_SHUTDOWN_REQUEST_H
#ifdef _WIN32
// Resolve conflict
#pragma push_macro("interface")
#undef interface
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <WinSock2.h>
#include <Windows.h>
// Resolve conflict
#pragma pop_macro("interface")
#ifdef GetClassInfo
#undef GetClassInfo
#endif
#elif defined __unix__
#include <semaphore.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#else
#error OS is not supported!
#endif
#include <string>
#include <filesystem>
#include "../../global/exec_dir_helper.h"
/**
* @brief Send a exit loop request using a local signaling solution (used when running as essential or standalone application).
* @attention There is no feedback that the shutdown was successful!
* @param[in] uiInstanceID The instance to shut down.
* @return Returns 'true' when the signal could be triggered, 'false' when no signal exists.
*/
inline bool RequestShutdown(uint32_t uiInstanceID = 1000u)
{
std::string m_ssSignalName = "SDV_EXIT_LOOP_REQUEST_" + std::to_string(uiInstanceID);
#ifdef _WIN32
HANDLE hEvent = OpenEventA(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, m_ssSignalName.c_str());
if (hEvent && hEvent != INVALID_HANDLE_VALUE)
{
SetEvent(hEvent);
CloseHandle(hEvent);
return true;
}
#elif defined __unix__
sem_t* pSemaphore = sem_open(m_ssSignalName.c_str(), 0);
if (pSemaphore && pSemaphore != SEM_FAILED)
{
// Since detecting whether or not the semaphore is triggered can only happen in the wait function, trigger 5 times with
// each 1 ms apart.
for (uint32_t uiIndex = 0; uiIndex < 5; uiIndex++)
{
sem_post(pSemaphore);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
sem_close(pSemaphore);
return true;
}
#endif
// Obviously there is no signal available... no standalone instance is running.
return false;
}
/**
* @brief Initialize the signal to receive a shutdown request and check whether a request was fired.
*/
class CShutdownRequestListener
{
public:
/**
* @brief Default constructor
*/
CShutdownRequestListener() = default;
/**
* @brief Constructor.
* @param[in] uiInstanceID The instance ID to check for.
*/
CShutdownRequestListener(uint32_t uiInstanceID);
/**
* @brief No support for copy constructor.
* @param[in] rListener Reference to the listener to copy.
*/
CShutdownRequestListener([[maybe_unused]] const CShutdownRequestListener& rListener) = delete;
/**
* @brief Move constructor
* @param[in] rListener Reference to the listener to move.
*/
CShutdownRequestListener(CShutdownRequestListener&& rListener) noexcept;
/**
* @brief Destructor
*/
~CShutdownRequestListener();
/**
* @brief Assignment operator is not supported.
* @param[in] rListener Reference to the listener to copy.
* @return Reference to the listener class.
*/
CShutdownRequestListener& operator=([[maybe_unused]] const CShutdownRequestListener& rListener) = delete;
/**
* @brief Move operator.
* @param[in] rListener Reference to the listener to move.
* @return Reference to the listener class.
*/
CShutdownRequestListener& operator=(CShutdownRequestListener&& rListener) noexcept;
/**
* @brief Check for validity.
* @return Returns whether the signal was initialized correctly.
*/
bool IsValid() const;
/**
* @brief Check whether a trigger occurred. To be called cyclicly.
* @param[in] uiWaitForMs The amount of ms to wait for the shutdown request.
* @return Returns 'true' when triggered; 'false' when not.
*/
bool HasTriggered(uint32_t uiWaitForMs) const;
private:
std::string m_ssSignalName; ///< The signal name used for the synchronization.
#ifdef _WIN32
HANDLE m_hEvent = INVALID_HANDLE_VALUE; ///< The event handle
#elif defined __unix__
sem_t* m_pSemaphore = nullptr; ///< The semaphore
#endif
};
#ifdef _WIN32
inline CShutdownRequestListener::CShutdownRequestListener(uint32_t uiInstanceID) :
m_ssSignalName("SDV_EXIT_LOOP_REQUEST_" + std::to_string(uiInstanceID))
{
// Prevent multiple instances by opening the named event first (this should fail)...
HANDLE hEvent = OpenEventA(SYNCHRONIZE, FALSE, m_ssSignalName.c_str());
if (hEvent)
CloseHandle(hEvent); // Another instance is already running. Do not create a new event.
else
m_hEvent = CreateEventA(nullptr, TRUE, FALSE, m_ssSignalName.c_str());
}
inline CShutdownRequestListener::CShutdownRequestListener(CShutdownRequestListener&& rListener) noexcept :
m_ssSignalName(std::move(rListener.m_ssSignalName)), m_hEvent(rListener.m_hEvent)
{
rListener.m_ssSignalName.clear();
rListener.m_hEvent = 0;
}
inline CShutdownRequestListener::~CShutdownRequestListener()
{
// Free the event object
if (m_hEvent && m_hEvent != INVALID_HANDLE_VALUE)
CloseHandle(m_hEvent);
}
inline CShutdownRequestListener& CShutdownRequestListener::operator=(CShutdownRequestListener&& rListener) noexcept
{
m_ssSignalName = std::move(rListener.m_ssSignalName);
m_hEvent = rListener.m_hEvent;
rListener.m_ssSignalName.clear();
rListener.m_hEvent = 0;
return *this;
}
inline bool CShutdownRequestListener::IsValid() const
{
return m_hEvent && m_hEvent != INVALID_HANDLE_VALUE;
}
inline bool CShutdownRequestListener::HasTriggered(uint32_t uiWaitForMs) const
{
return m_hEvent && m_hEvent != INVALID_HANDLE_VALUE && WaitForSingleObject(m_hEvent, uiWaitForMs) == WAIT_OBJECT_0;
}
#elif defined __unix__
inline CShutdownRequestListener::CShutdownRequestListener(uint32_t uiInstanceID) :
m_ssSignalName("SDV_EXIT_LOOP_REQUEST_" + std::to_string(uiInstanceID))
{
// Create semaphore object
sem_unlink(m_ssSignalName.c_str());
m_pSemaphore = sem_open(m_ssSignalName.c_str(), O_CREAT | O_EXCL, 0777 /*O_RDWR*/, 0);
}
inline CShutdownRequestListener::CShutdownRequestListener(CShutdownRequestListener&& rListener) noexcept :
m_ssSignalName(std::move(rListener.m_ssSignalName)), m_pSemaphore(rListener.m_pSemaphore)
{
rListener.m_ssSignalName.clear();
rListener.m_pSemaphore = 0;
}
inline CShutdownRequestListener::~CShutdownRequestListener()
{
if (m_pSemaphore && m_pSemaphore != SEM_FAILED)
sem_unlink(m_ssSignalName.c_str());
}
inline CShutdownRequestListener& CShutdownRequestListener::operator=(CShutdownRequestListener&& rListener) noexcept
{
m_ssSignalName = std::move(rListener.m_ssSignalName);
m_pSemaphore = rListener.m_pSemaphore;
rListener.m_ssSignalName.clear();
rListener.m_pSemaphore = nullptr;
return *this;
}
inline bool CShutdownRequestListener::IsValid() const
{
return m_pSemaphore && m_pSemaphore != SEM_FAILED;
}
inline bool CShutdownRequestListener::HasTriggered(uint32_t uiWaitForMs) const
{
// Get the time from the realtime clock
timespec sTimespec{};
if (clock_gettime(CLOCK_REALTIME, &sTimespec) == -1)
return false;
uint64_t uiTimeNs = sTimespec.tv_nsec + uiWaitForMs * 1000000ull;
sTimespec.tv_nsec = uiTimeNs % 1000000000ull;
sTimespec.tv_sec += uiTimeNs / 1000000000ull;
// Wait for the semaphore
return m_pSemaphore && m_pSemaphore != SEM_FAILED && sem_timedwait(m_pSemaphore, &sTimespec) == 0;
}
#endif
#endif // !defined LOCAL_SHUTDOWN_REQUEST_H

View File

@@ -0,0 +1,71 @@
#include "log_csv_writer.h"
#include <sstream>
CLogCSVWriter::CLogCSVWriter(std::shared_ptr<std::ostream> ostream, const char separator /*= ';'*/)
: m_streamOutput(std::move(ostream))
, m_cSeparator(separator)
{
if ((m_cSeparator <= 0x08) || (m_cSeparator >= 0x0A && m_cSeparator <= 0x1F)
|| (m_cSeparator >= 0x30 && m_cSeparator <= 0x39) || (m_cSeparator >= 0x41 && m_cSeparator <= 0x5A)
|| (m_cSeparator >= 0x61 && m_cSeparator <= 0x7A) || static_cast<uint8_t>(m_cSeparator) >= 0x7F
|| m_cSeparator == '\"' || m_cSeparator == '.' || m_cSeparator == ':' || m_cSeparator == ' ')
{
throw SForbiddenSeparatorException((std::string("Forbidden Separator '") + m_cSeparator + "'").c_str());
}
std::string intro = std::string("sep=") + m_cSeparator + "\nTimestamp" + m_cSeparator + "Severity" + m_cSeparator
+ "Tag" + m_cSeparator + "Message\n";
std::unique_lock<std::mutex> lock(m_mtxOutput);
*m_streamOutput << intro;
m_streamOutput->flush();
}
void CLogCSVWriter::Write(const std::string& message, const sdv::core::ELogSeverity severity,
const std::chrono::time_point<std::chrono::system_clock> timestamp)
{
std::string output = GetDateTime(timestamp) + m_cSeparator + GetSeverityString(severity) +
m_cSeparator + "\"" + m_cSeparator + "\"" + Escape(message) + "\"\n";
std::unique_lock<std::mutex> lock(m_mtxOutput);
*m_streamOutput << output;
m_streamOutput->flush();
}
std::string CLogCSVWriter::GetDateTime(const std::chrono::time_point<std::chrono::system_clock> timestamp)
{
using namespace std::chrono;
std::stringstream output;
microseconds micros = duration_cast<microseconds>(timestamp.time_since_epoch());
uint32_t fractionalSeconds = micros.count() % 1000000;
std::string timeFormat = "%Y.%m.%d %H:%M:%S.";
std::time_t time = system_clock::to_time_t(timestamp);
output << std::put_time(std::localtime(&time), timeFormat.c_str());
output << std::setw(6) << std::setfill('0') << fractionalSeconds;
return output.str();
}
std::string CLogCSVWriter::GetSeverityString(sdv::core::ELogSeverity severity)
{
switch (severity)
{
case sdv::core::ELogSeverity::trace: return "Trace";
case sdv::core::ELogSeverity::debug: return "Debug";
case sdv::core::ELogSeverity::info: return "Info";
case sdv::core::ELogSeverity::warning: return "Warning";
case sdv::core::ELogSeverity::error: return "Error";
case sdv::core::ELogSeverity::fatal: return "Fatal";
default: return "Unknown";
}
}
std::string CLogCSVWriter::Escape(const std::string& toEscape)
{
std::string escaped = toEscape;
const std::string with("\"\"");
for (std::string::size_type pos{}; std::string::npos != (pos = escaped.find('\"', pos)); pos += with.length())
{
escaped.replace(pos, 1, with.data(), with.length());
}
return escaped;
}

View File

@@ -0,0 +1,68 @@
#ifndef LOG_CSV_WRITER_H
#define LOG_CSV_WRITER_H
#include <iostream>
#include <chrono>
#include <string>
#include <interfaces/log.h>
#include <mutex>
/**
* @brief Writes CSV entries to a given stream.
*/
class CLogCSVWriter
{
public:
/**
* @brief Constructs a CSV writer.
* @param[in] ostream stream the CSV writer will write to.
* @param[in] separator sets the separator of the CSV file, default is ';'.
*/
CLogCSVWriter(std::shared_ptr<std::ostream> ostream, const char separator = ';');
/**
* @brief Creates a log entry in the log.
* @param[in] message message of the log entry.
* @param[in] severity severity of the log entry.
* @param[in] timestamp timestamp of the log entry.
*/
void Write(const std::string& message, const sdv::core::ELogSeverity severity,
const std::chrono::time_point<std::chrono::system_clock> timestamp);
/**
* @brief Creates a string of the given timestamp including nano seconds.
* @param[in] timestamp Timestamp to be converted to a string.
* @return Returns the string representing the timestamp including nano seconds.
*/
std::string GetDateTime(const std::chrono::time_point<std::chrono::system_clock> timestamp);
/**
* @brief Creates string from the given severity.
* @param[in] severity Severity to be converted to a string.
* @return Returns the string representing the severity.
*/
static std::string GetSeverityString(sdv::core::ELogSeverity severity);
/**
* @brief Exception if an invalid separator is set.
*/
struct SForbiddenSeparatorException : public std::runtime_error
{
/**
* @brief Construct a forbidden separator Exception object.
* @param[in] message The reason for throwing this exception.
*/
SForbiddenSeparatorException(const char* message)
: std::runtime_error(message)
{}
};
private:
static std::string Escape(const std::string& toEscape);
std::shared_ptr<std::ostream> m_streamOutput; ///< Output stream
const char m_cSeparator; ///< The separator character
mutable std::mutex m_mtxOutput; ///< Protect stream access
};
#endif // !defined LOG_CSV_WRITER_H

View File

@@ -0,0 +1,151 @@
#include "logger.h"
#include <sstream>
#include "sdv_core.h"
#include "../../global/exec_dir_helper.h"
#ifdef __unix__
#include <syslog.h>
#endif
#ifdef __GNUC__
#include <unistd.h> // for getpid
#endif
CLogger::~CLogger()
{
#ifdef __unix__
if (m_bLogOpen) closelog();
#endif
}
void CLogger::Log(sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
/*in*/ sdv::process::TProcessID tProcessID, /*in*/ const sdv:: u8string& ssObjectName, /*in*/ const sdv::u8string& ssMessage)
{
if (static_cast<uint32_t>(eSeverity) >= static_cast<uint32_t>(m_eViewFilter))
{
if (tProcessID) std::clog << "[PID#" << static_cast<int64_t>(tProcessID) << "] ";
if (!ssObjectName.empty()) std::clog << ssObjectName << " ";
switch (eSeverity)
{
case sdv::core::ELogSeverity::debug:
std::clog << "Debug: " << ssMessage << std::endl;
break;
case sdv::core::ELogSeverity::trace:
std::clog << "Trace: " << ssMessage << std::endl;
break;
case sdv::core::ELogSeverity::info:
std::clog << "Info: " << ssMessage << std::endl;
break;
case sdv::core::ELogSeverity::warning:
std::clog << "Warning: " << ssMessage << std::endl;
break;
case sdv::core::ELogSeverity::error:
std::clog << "Error: " << ssMessage << std::endl;
break;
case sdv::core::ELogSeverity::fatal:
std::clog << "Fatal: " << ssMessage << std::endl;
break;
default:
break;
}
}
// Apply the log filter
if (static_cast<uint32_t>(eSeverity) < static_cast<uint32_t>(m_eFilter)) return;
std::unique_lock<std::recursive_mutex> lock(m_mtxLogger);
if (m_ssProgramtag.empty())
m_ssProgramtag = std::string("SDV_LOG_") + std::to_string(getpid());
sdv::u8string ssExtendedMessage;
ssExtendedMessage += ssSrcFile + "(" + std::to_string(uiSrcLine) + "): " + ssMessage;
#ifdef _WIN32
if (!m_ptrWriter)
{
std::string logFileName = m_ssProgramtag + "_" +
GetDateTime(std::chrono::system_clock::now()) + ".log";
auto path = GetExecDirectory() / logFileName;
auto stream = std::make_shared<std::ofstream>(path, std::ios_base::out | std::ios_base::app);
if (!stream->is_open())
{
std::cerr << "ERROR: Log file cannot be created!" << std::endl;
return;
}
m_ptrWriter = std::move(std::make_unique<CLogCSVWriter>(stream));
if (!m_ptrWriter)
{
std::cerr << "ERROR: Cannot create CSV write object!" << std::endl;
return;
}
}
m_ptrWriter->Write(ssExtendedMessage, eSeverity, std::chrono::system_clock::now());
#elif defined __unix__
if (!m_bLogOpen)
openlog(m_ssProgramtag.c_str(), LOG_PID | LOG_CONS, LOG_USER);
m_bLogOpen = true;
switch(eSeverity)
{
case sdv::core::ELogSeverity::trace : syslog(LOG_NOTICE, "%s", ssExtendedMessage.c_str()); break;
case sdv::core::ELogSeverity::debug : syslog(LOG_DEBUG, "%s", ssExtendedMessage.c_str()); break;
case sdv::core::ELogSeverity::info : syslog(LOG_INFO, "%s", ssExtendedMessage.c_str()); break;
case sdv::core::ELogSeverity::warning : syslog(LOG_WARNING, "%s", ssExtendedMessage.c_str()); break;
case sdv::core::ELogSeverity::error : syslog(LOG_ERR, "%s", ssExtendedMessage.c_str()); break;
case sdv::core::ELogSeverity::fatal : syslog(LOG_CRIT, "%s", ssExtendedMessage.c_str()); break;
default: std::cerr << "ERROR: Logging level is undefined!"; break;
}
#else
#error OS not supported!
#endif
}
void CLogger::SetProgramTag(const sdv::u8string& ssTag)
{
std::unique_lock<std::recursive_mutex> lock(m_mtxLogger);
if (m_ssProgramtag.empty())
m_ssProgramtag = ssTag;
}
sdv::u8string CLogger::GetProgramTag() const
{
return m_ssProgramtag;
}
void CLogger::SetLogFilter(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ sdv::core::ELogSeverity eViewSeverity)
{
m_eFilter = eSeverity;
m_eViewFilter = eViewSeverity;
}
sdv::core::ELogSeverity CLogger::GetLogFilter() const
{
return m_eFilter;
}
sdv::core::ELogSeverity CLogger::GetViewFilter() const
{
return m_eViewFilter;
}
std::string CLogger::GetDateTime(std::chrono::time_point<std::chrono::system_clock> timestamp)
{
using namespace std::chrono;
std::stringstream output;
microseconds micros = duration_cast<microseconds>(timestamp.time_since_epoch());
std::string timeFormat = "_%Y.%m.%d_%H-%M-%S";
std::time_t time = system_clock::to_time_t(timestamp);
output << std::put_time(std::localtime(&time), timeFormat.c_str());
return output.str();
}
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
CLogger& CLoggerService::GetDefaultLogger()
{
return ::GetDefaultLogger();
}
#endif

128
sdv_services/core/logger.h Normal file
View File

@@ -0,0 +1,128 @@
#ifndef LOGGER_H
#define LOGGER_H
#include <interfaces/log.h>
#include <support/interface_ptr.h>
#include <support/component_impl.h>
#include "log_csv_writer.h"
/**
* @brief Logger class implementation for Windows to enable logging in a logfile.
*/
class CLogger : public sdv::IInterfaceAccess, public sdv::core::ILogger, public sdv::core::ILoggerConfig
{
public:
/**
* @brief Default constructor
*/
CLogger() = default;
/**
* @brief Destructor
*/
~CLogger();
// Interface table
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::core::ILogger)
SDV_INTERFACE_ENTRY(sdv::core::ILoggerConfig)
END_SDV_INTERFACE_MAP()
/**
* @brief Log a message to the SDV log. Overload of sdv::core::ILogger::Log.
* @param[in] eSeverity Severity level of the log message which will be logged, e.g. info, warning, error etc.
* @param[in] ssSrcFile The source file that caused the log entry.
* @param[in] uiSrcLine The line number in the source file that caused the log entry.
* @param[in] tProcessID Process ID of the process reporting this log entry.
* @param[in] ssObjectName Name of the object if the log entry is supplied by a component.
* @param[in] ssMessage The log message that will be logged.
*/
virtual void Log(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
/*in*/ sdv::process::TProcessID tProcessID, /*in*/ const sdv:: u8string& ssObjectName, /*in*/ const sdv::u8string& ssMessage) override;
/**
* @brief Initialize the logging from SDV platform abstraction. Overload of sdv::core::ILoggerConfig::SetProgramTag.
* @details This function needs to be called before starting to log.
* @param[in] ssTag Provided tag to create log.
*/
virtual void SetProgramTag(/*in*/ const sdv::u8string& ssTag) override;
/**
* @brief Get the program tag used for logging. Overload of sdv::core::ILoggerConfig::GetProgramTag.
* @return The string containing the program tag.
*/
virtual sdv::u8string GetProgramTag() const override;
/**
* @brief Filter the log messages based on severity. Overload of sdv::core::ILoggerConfig::SetLogFilter.
* @param[in] eSeverity The severity level to use as a lowest level for logging. Default is "info" meaning, that
* debug and trace messages will not be logged.
* @param[in] eViewSeverity The severity level to use as a lowest level for viewing. Default is "error" meaning, that
* debug, trace, info, warning and error messages will not be shown in console output.
*/
virtual void SetLogFilter(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ sdv::core::ELogSeverity eViewSeverity) override;
/**
* @brief Get the current log severity filter level. Overload of sdv::core::ILoggerConfig::GetLogFilter.
* @return The severity level of the log filter.
*/
virtual sdv::core::ELogSeverity GetLogFilter() const override;
/**
* @brief Get the current view severity level. Overload of sdv::core::ILoggerConfig::GetViewFilter.
* @return The severity level of the view filter.
*/
virtual sdv::core::ELogSeverity GetViewFilter() const override;
private:
/**
* @brief Creates a string of the given timestamp without nano seconds.
* @param[in] timestamp Timestamp to be converted to a string.
* @return Returns the string representing the timestamp without nano seconds.
*/
static std::string GetDateTime(std::chrono::time_point<std::chrono::system_clock> timestamp);
std::recursive_mutex m_mtxLogger; ///< Mutex for logging
sdv::core::ELogSeverity m_eFilter = sdv::core::ELogSeverity::info; ///< Severity filter for logging
sdv::core::ELogSeverity m_eViewFilter = sdv::core::ELogSeverity::info; ///< Severity filter for viewing
std::string m_ssProgramtag; ///< Program tag to use for logging.
#ifdef _WIN32
std::unique_ptr<CLogCSVWriter> m_ptrWriter; ///< Pointer to CSVWriter
#elif defined __unix__
bool m_bLogOpen = false; ///< Check if the logger is opened.
#else
#error OS currently not supported!
#endif
};
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
/**
* @brief Logger service
*/
class CLoggerService : public sdv::CSdvObject
{
public:
CLoggerService() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_CHAIN_MEMBER(GetDefaultLogger())
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_NAME("DefaultLoggerService")
DECLARE_OBJECT_SINGLETON()
/**
* @brief Get access to the repository.
* @return Returns a reference to the one repository of this module.
*/
static CLogger& GetDefaultLogger();
};
DEFINE_SDV_OBJECT_NO_EXPORT(CLoggerService)
#endif
#endif // !defined LOGGER_H

View File

@@ -0,0 +1,77 @@
#include "logger_control.h"
#ifdef _WIN32
#include <process.h> // Needed for _getpid
#endif
CLoggerControl::~CLoggerControl()
{
// Reset the logger. This will finialize an open log.
SetLogger(nullptr);
}
void CLoggerControl::Log(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
/*in*/ const sdv::process::TProcessID tProcessID, /*in*/ const sdv::u8string& ssObjectName, /*in*/ const sdv::u8string& ssMessage)
{
std::unique_lock<std::mutex> lock(m_mtxPrematureLog);
// Is a registered logger available?
if (m_pLogger)
{
sdv::core::ILogger* pLogger = m_pLogger;
lock.unlock();
pLogger->Log(eSeverity, ssSrcFile, uiSrcLine, tProcessID, ssObjectName, ssMessage);
return;
}
// Store the log entry for later use. Store at the most 1000 messages.
if (m_queuePrematureLog.size() >= 1000) return;
if (m_queuePrematureLog.size() == 999)
{
#ifdef _WIN32
sdv::process::TProcessID tProcessIDLocal = _getpid();
#elif defined __unix__
sdv::process::TProcessID tProcessIDLocal = getpid();
#else
#error The OS currently is not supported!
#endif
SLogEntry entryTruncated{ sdv::core::ELogSeverity::warning, "", 0, tProcessIDLocal, "", "Log truncated..." };
m_queuePrematureLog.push(entryTruncated);
return;
}
SLogEntry entry{ eSeverity, ssSrcFile, uiSrcLine, tProcessID, ssObjectName, ssMessage };
m_queuePrematureLog.push(entry);
}
void CLoggerControl::SetLogger(sdv::core::ILogger* pLogger)
{
std::unique_lock<std::mutex> lock(m_mtxPrematureLog);
if (pLogger == m_pLogger) return;
// When assigning a logger, write the stored entries to the log
#ifdef _WIN32
sdv::process::TProcessID tProcessID = _getpid();
#elif defined __unix__
sdv::process::TProcessID tProcessID = getpid();
#else
#error The OS currently is not supported!
#endif
if (pLogger)
{
pLogger->Log(sdv::core::ELogSeverity::info, "", 0, tProcessID, "", "Begin logging...");
while (!m_queuePrematureLog.empty())
{
SLogEntry& rentry = m_queuePrematureLog.front();
pLogger->Log(rentry.eSeverity, rentry.ssSrcFile, rentry.uiSrcLine, rentry.tProcessID, rentry.ssObjectName,
rentry.ssMessage);
m_queuePrematureLog.pop();
}
}
else // Finish the log
{
m_pLogger->Log(sdv::core::ELogSeverity::info, "", 0, tProcessID, "", "End logging...");
}
// Store the new logger pointer
m_pLogger = pLogger;
}

View File

@@ -0,0 +1,69 @@
#ifndef LOGGER_CONTROL_H
#define LOGGER_CONTROL_H
#include <support/interface_ptr.h>
#include <interfaces/log.h>
#include <queue>
#include <mutex>
#include <utility>
/**
* @brief Logger control class.
*/
class CLoggerControl : public sdv::IInterfaceAccess, public sdv::core::ILogger
{
public:
/**
* @brief Default constructor
*/
CLoggerControl() = default;
/**
* @brief Destructor
*/
~CLoggerControl();
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::core::ILogger)
END_SDV_INTERFACE_MAP()
/**
* @brief Log a message to the SDV log. Overload of sdv::core::ILogger::Log.
* @param[in] eSeverity Severity level of the log message which will be logged, e.g. info, warning, error etc.
* @param[in] ssSrcFile The source file that caused the log entry.
* @param[in] uiSrcLine The line number in the source file that caused the log entry.
* @param[in] tProcessID Process ID of the process reporting this log entry.
* @param[in] ssObjectName Name of the object if the log entry is supplied by a component.
* @param[in] ssMessage The log message that will be logged.
*/
virtual void Log(/*in*/ sdv::core::ELogSeverity eSeverity, /*in*/ const sdv::u8string& ssSrcFile, /*in*/ uint32_t uiSrcLine,
/*in*/ const sdv::process::TProcessID tProcessID, /*in*/ const sdv::u8string& ssObjectName,
/*in*/ const sdv::u8string& ssMessage) override;
/**
* @brief Set the logger to use for logging.
* @param[in] pLogger
*/
void SetLogger(sdv::core::ILogger* pLogger);
private:
/**
* @brief Log entry to store.
*/
struct SLogEntry
{
sdv::core::ELogSeverity eSeverity; ///< Severity level of the log message.
sdv::u8string ssSrcFile; ///< The source file that caused the log entry.
uint32_t uiSrcLine; ///< The line number in the source file that caused the log entry.
sdv::process::TProcessID tProcessID; ///< Process ID of the process submitting the log entry.
sdv::u8string ssObjectName; ///< Name of the object if the log entry is submitted by an object.
sdv::u8string ssMessage; ///< The log message that will be logged.
};
mutable std::mutex m_mtxPrematureLog; ///< Protect the logger queue access.
std::queue<SLogEntry> m_queuePrematureLog; ///< Logger queue to store messages before a logger was assigned.
sdv::core::ILogger* m_pLogger = nullptr; ///< Interface for the actual logger.
};
#endif // !defined LOGGER_CONTROL_H

View File

@@ -0,0 +1,62 @@
#include "memory.h"
sdv::pointer<uint8_t> CMemoryManager::Allocate(uint32_t uiLength)
{
return sdv::internal::make_ptr<uint8_t>(this, uiLength);
}
void* CMemoryManager::Alloc(size_t nSize)
{
void* pAlloc = malloc(nSize);
#ifdef MEMORY_TRACKER
std::unique_lock<std::mutex> lock(m_mtxTracker);
if (pAlloc)
m_mapTracker.insert(std::make_pair(pAlloc, nSize));
#endif
return pAlloc;
}
void* CMemoryManager::Realloc(void* pData, size_t nSize)
{
#ifdef MEMORY_TRACKER
std::unique_lock<std::mutex> lock(m_mtxTracker);
auto itAlloc = m_mapTracker.find(pData);
if (pData && itAlloc == m_mapTracker.end())
{
std::cout << "Illegal request for resizing memory at location 0x" << (void*)pData << std::endl;
return nullptr;
}
lock.unlock();
#endif
void* pAlloc = realloc(pData, nSize);
#ifdef MEMORY_TRACKER
if (pAlloc)
{
lock.lock();
m_mapTracker.erase(pData);
m_mapTracker.insert(std::make_pair(pAlloc, nSize));
}
#endif
return pAlloc;
}
void CMemoryManager::Free(void* pData)
{
#ifdef MEMORY_TRACKER
std::unique_lock<std::mutex> lock(m_mtxTracker);
auto itAlloc = m_mapTracker.find(pData);
if (itAlloc == m_mapTracker.end())
{
std::cout << "Illegal request for freeing memory at location 0x" << (void*)pData << std::endl;
return;
}
else
m_mapTracker.erase(itAlloc);
lock.unlock();
#endif
free(pData);
}

View File

@@ -0,0 +1,57 @@
#ifndef MEMORY_H
#define MEMORY_H
#include <map>
#include <mutex>
#include <support/pointer.h>
#include <interfaces/mem.h>
#include <support/component_impl.h>
#include <support/mem_access.h>
/**
* @brief Memory Manager service
*/
class CMemoryManager : public sdv::core::IMemoryAlloc, public sdv::IInterfaceAccess, public sdv::internal::IInternalMemAlloc
{
public:
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::core::IMemoryAlloc)
END_SDV_INTERFACE_MAP()
/**
* @brief Allocate a memory block of the provided length. Overload of sdv::core::IMemoryAlloc::Allocate.
* @param[in] uiLength The length of the memory block to allocate.
* @return Smart pointer to the memory allocation or NULL when allocating was not possible.
*/
virtual sdv::pointer<uint8_t> Allocate(/*in*/ uint32_t uiLength) override;
private:
/**
* @brief Allocate memory. Overload of internal::IInternalMemAlloc::Alloc.
* @param[in] nSize The size of the memory to allocate (in bytes).
* @return Pointer to the memory allocation or NULL when memory allocation failed.
*/
virtual void* Alloc(size_t nSize) override;
/**
* @brief Reallocate memory. Overload of internal::IInternalMemAlloc::Realloc.
* @param[in] pData Pointer to a previous allocation or NULL when no previous allocation was available.
* @param[in] nSize The size of the memory to allocate (in bytes).
* @return Pointer to the memory allocation or NULL when memory allocation failed.
*/
virtual void* Realloc(void* pData, size_t nSize) override;
/**
* @brief Free a memory allocation. Overload of internal::IInternalMemAlloc::Free.
* @param[in] pData Pointer to a previous allocation.
*/
virtual void Free(void* pData) override;
#ifdef MEMORY_TRACKER
std::mutex m_mtxTracker; ///< Synchronize map access
std::map<void*, size_t> m_mapTracker; ///< Memory manager allocation tracker
#endif
};
#endif // !define MEMORY_H

View File

@@ -0,0 +1,374 @@
#include "module.h"
#include "sdv_core.h"
#include <support/local_service_access.h>
#include <functional>
#include <algorithm>
#ifdef _WIN32
// Resolve conflict
#pragma push_macro("interface")
#pragma push_macro("GetObject")
#undef interface
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <WinSock2.h>
#include <Windows.h>
#include <objbase.h>
// Resolve conflict
#pragma pop_macro("interface")
#pragma pop_macro("GetObject")
#ifdef GetClassInfo
#undef GetClassInfo
#endif
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
#endif // defined _WIN32
#ifdef __unix__
#include <dlfcn.h>
#endif
CModuleInst::CModuleInst(const std::filesystem::path& rpathModuleConfigPath, const std::filesystem::path& rpathModule) noexcept :
m_pathModuleConfigPath(rpathModuleConfigPath)
{
if (!rpathModule.empty())
Load(rpathModule);
}
CModuleInst::~CModuleInst()
{
Unload(true);
}
bool CModuleInst::IsValid() const noexcept
{
return m_tModuleID != 0 && m_pFactory;
}
std::string CModuleInst::GetDefaultObjectName(const std::string& ssClassName) const
{
auto itClass = m_mapClassInfo.find(ssClassName);
if (itClass == m_mapClassInfo.end()) return std::string();
return itClass->second.ssDefaultObjectName.empty() ? itClass->second.ssClassName : itClass->second.ssDefaultObjectName;
}
bool CModuleInst::IsSingleton(const std::string& ssClassName) const
{
auto itClass = m_mapClassInfo.find(ssClassName);
if (itClass == m_mapClassInfo.end()) return false;
return (itClass->second.uiFlags & static_cast<uint32_t>(sdv::EObjectFlags::singleton)) ? true : false;
}
std::vector<std::string> CModuleInst::GetAvailableClasses() const
{
std::vector<std::string> vecClasses;
for (const auto& rvtClass : m_mapClassInfo)
vecClasses.push_back(rvtClass.first);
return vecClasses;
}
sdv::IInterfaceAccess* CModuleInst::CreateObject(const std::string& ssClassName)
{
if(!m_pFactory)
{
return nullptr;
}
return m_pFactory->CreateObject(ssClassName.c_str());
}
void CModuleInst::DestroyObject(sdv::IInterfaceAccess* object)
{
if(m_pFactory)
{
m_pFactory->DestroyObject(object);
}
}
std::filesystem::path CModuleInst::GetModuleConfigPath() const
{
return m_pathModuleConfigPath;
}
std::filesystem::path CModuleInst::GetModulePath() const
{
return m_pathModule;
}
sdv::core::TModuleID CModuleInst::GetModuleID() const
{
return m_tModuleID;
}
sdv::core::SModuleInfo CModuleInst::GetModuleInfo() const
{
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
sdv::core::SModuleInfo sInfo{};
sInfo.tModuleID = m_tModuleID;
sInfo.ssPath = m_pathModule.filename().generic_u8string();
sInfo.ssFilename = m_pathModule.filename().generic_u8string();
sInfo.uiVersion = m_uiIfcVersion;
sInfo.bActive = m_fnActiveObjects ? m_fnActiveObjects() : false;
return sInfo;
}
std::optional<sdv::SClassInfo> CModuleInst::GetClassInfo(const std::string& rssClassName) const
{
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
const auto itClass = m_mapClassInfo.find(rssClassName);
if (itClass == m_mapClassInfo.end()) return {};
std::optional<sdv::SClassInfo> optClassInfo = itClass->second;
// Add the module path to the result
optClassInfo->ssModulePath = m_pathModule.generic_u8string();
return itClass->second;
}
bool CModuleInst::Load(const std::filesystem::path& rpathModule) noexcept
{
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
// Cannot load again.
if (m_tModuleID) return false;
m_pathModule = rpathModule;
// Core services bypass.
if (rpathModule == "core_services.sdv")
{
m_tModuleID = sdv::core::tCoreLibModule;
m_fnActiveObjects = &::HasActiveObjects;
m_fnGetFactory = &::GetModuleFactory;
m_fnGetManifest = &::GetManifest;
}
else
{
#ifdef _WIN32
m_tModuleID = reinterpret_cast<uint64_t>(LoadLibrary(rpathModule.native().c_str()));
#elif defined __unix__
m_tModuleID = reinterpret_cast<uint64_t>(dlopen(rpathModule.native().c_str(), RTLD_LAZY));
#else
#error OS not supported!
#endif
if (!m_tModuleID)
{
#ifdef _WIN32
std::string ssError(1024, '\0');
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
&ssError.front(),
static_cast<DWORD>(ssError.size()), NULL);
ssError[1023] = '\0';
ssError.resize(strlen(ssError.c_str()));
#elif defined __unix__
std::string ssError = dlerror();
#else
#error OS not supported!
#endif
SDV_LOG_ERROR("Error opening SDV module: \"", rpathModule.generic_u8string(), "\" error: ", ssError);
return 0;
}
// Check whether the module exposes the necessary functions
#ifdef _WIN32
m_fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(GetProcAddress(reinterpret_cast<HMODULE>(m_tModuleID), "GetModuleFactory"));
m_fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(GetProcAddress(reinterpret_cast<HMODULE>(m_tModuleID), "HasActiveObjects"));
m_fnGetManifest = reinterpret_cast<TFNGetManifest*>(GetProcAddress(reinterpret_cast<HMODULE>(m_tModuleID), "GetManifest"));
#elif defined __unix__
m_fnGetFactory = reinterpret_cast<TFNGetModuleFactory*>(dlsym(reinterpret_cast<void*>(m_tModuleID), "GetModuleFactory"));
m_fnActiveObjects = reinterpret_cast<TFNHasActiveObjects*>(dlsym(reinterpret_cast<void*>(m_tModuleID), "HasActiveObjects"));
m_fnGetManifest = reinterpret_cast<TFNGetManifest*>(dlsym(reinterpret_cast<void*>(m_tModuleID), "GetManifest"));
#else
#error OS not supported!
#endif
if (!m_fnGetFactory || !m_fnActiveObjects || !m_fnGetManifest || !m_fnGetManifest())
{
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(),
" error: The module doesn't expose access functions!");
Unload(true);
return 0;
}
}
// Get the manifest
std::string ssManifest = m_fnGetManifest();
try
{
CParserTOML parser(ssManifest);
// Check for the interface version - currently must be equal.
auto ptrVersion = parser.GetRoot().GetDirect("Interface.Version");
if (ptrVersion) m_uiIfcVersion = ptrVersion->GetValue();
if (!ptrVersion || m_uiIfcVersion != SDVFrameworkInterfaceVersion)
{
// Incompatible interface.
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(), " error: incompatible version ", m_uiIfcVersion,
" (required ", SDVFrameworkInterfaceVersion, ")");
Unload(true);
return false;
}
// Get a pointer to the factory for the current interface.
m_pFactory = sdv::TInterfaceAccessPtr(m_fnGetFactory(SDVFrameworkInterfaceVersion)).GetInterface<sdv::IObjectFactory>();
if (!m_pFactory)
{
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(), " error: no object factory available");
Unload(true);
return false;
}
// Get available classes
auto ptrComponents = parser.GetRoot().GetDirect("Component");
if (!ptrComponents || !ptrComponents->GetArray())
{
SDV_LOG_ERROR("Error opening SDV module: ", rpathModule.generic_u8string(), " error: no components available");
Unload(true);
return false;
}
for (std::uint32_t uiIndex = 0; uiIndex < ptrComponents->GetArray()->GetCount(); uiIndex++)
{
// Fill in the class info.
sdv::SClassInfo sInfo{};
auto ptrComponent = ptrComponents->GetArray()->Get(uiIndex);
if (!ptrComponent) continue;
auto ptrClassName = ptrComponent->GetDirect("Class");
if (!ptrClassName) continue;
sInfo.ssClassName = static_cast<std::string>(ptrClassName->GetValue());
auto ptrAliases = ptrComponent->GetDirect("Aliases");
if (ptrAliases)
{
auto ptrAliasesArray = ptrAliases->GetArray();
for (uint32_t uiAliasIndex = 0; ptrAliasesArray && uiAliasIndex < ptrAliasesArray->GetCount(); uiAliasIndex++)
{
auto ptrClassAlias = ptrAliasesArray->Get(uiAliasIndex);
if (ptrClassAlias)
sInfo.seqClassAliases.push_back(static_cast<sdv::u8string>(ptrClassAlias->GetValue()));
}
}
auto ptrDefaultName = ptrComponent->GetDirect("DefaultName");
if (ptrDefaultName) sInfo.ssDefaultObjectName = static_cast<std::string>(ptrDefaultName->GetValue());
else sInfo.ssDefaultObjectName = sInfo.ssClassName;
auto ptrType = ptrComponent->GetDirect("Type");
if (!ptrType) continue;
std::string ssType = static_cast<std::string>(ptrType->GetValue());
if (ssType == "System") sInfo.eType = sdv::EObjectType::SystemObject;
else if (ssType == "Device") sInfo.eType = sdv::EObjectType::Device;
else if (ssType == "BasicService") sInfo.eType = sdv::EObjectType::BasicService;
else if (ssType == "ComplexService") sInfo.eType = sdv::EObjectType::ComplexService;
else if (ssType == "App") sInfo.eType = sdv::EObjectType::Application;
else if (ssType == "Proxy") sInfo.eType = sdv::EObjectType::Proxy;
else if (ssType == "Stub") sInfo.eType = sdv::EObjectType::Stub;
else if (ssType == "Utility") sInfo.eType = sdv::EObjectType::Utility;
else continue;
auto ptrSingleton = ptrComponent->GetDirect("Singleton");
if (ptrSingleton && static_cast<bool>(ptrSingleton->GetValue()))
sInfo.uiFlags = static_cast<uint32_t>(sdv::EObjectFlags::singleton);
auto ptrDependencies = ptrComponent->GetDirect("Dependencies");
if (ptrDependencies)
{
auto ptrDependencyArray = ptrDependencies->GetArray();
for (uint32_t uiDependencyIndex = 0; ptrDependencyArray && uiDependencyIndex < ptrDependencyArray->GetCount();
uiDependencyIndex++)
{
auto ptrDependsOn = ptrDependencyArray->Get(uiDependencyIndex);
if (ptrDependsOn)
sInfo.seqDependencies.push_back(static_cast<sdv::u8string>(ptrDependsOn->GetValue()));
}
}
m_mapClassInfo[sInfo.ssClassName] = sInfo;
}
}
catch (const sdv::toml::XTOMLParseException&)
{
SDV_LOG_ERROR("Manifest interpretation error for: \"", m_pathModule.filename().generic_u8string(), "\"");
return false;
}
SDV_LOG_INFO("Successfully loaded SDV module: \"", m_pathModule.filename().generic_u8string(), "\"");
return true;
}
bool CModuleInst::Unload(bool bForce) noexcept
{
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
// Request the repository to unload any running objects
try
{
GetRepository().DestroyModuleObjects(m_tModuleID);
}
catch (const sdv::XSysExcept&)
{
}
catch (const std::exception&)
{
}
// Check for a active objects first
bool bHasActive = m_fnActiveObjects && m_fnActiveObjects();
if (!bForce && bHasActive)
{
SDV_LOG_WARNING("Trying to unload module \"", m_pathModule.filename().generic_u8string(),
"\" but there are still active objects. Unloading the module will be blocked.");
return false;
}
// Clear the members
m_fnActiveObjects = nullptr;
m_fnGetFactory = nullptr;
m_pFactory = nullptr;
m_mapClassInfo.clear();
m_uiIfcVersion = 0u;
// Unload the module
if (!bHasActive)
{
if (m_tModuleID && m_tModuleID != sdv::core::tCoreLibModule)
{
#ifdef _WIN32
FreeLibrary(reinterpret_cast<HMODULE>(m_tModuleID));
#elif defined __unix__
dlclose(reinterpret_cast<void*>(m_tModuleID));
#else
#error OS not supported!
#endif
SDV_LOG_INFO("Successfully unloaded SDV module: \"", m_pathModule.filename().generic_u8string(), "\"");
}
} else
{
SDV_LOG_ERROR("Trying to force to unload module \"", m_pathModule.filename().generic_u8string(),
"\" but there are still active objects. The module will not be unloaded! "
"However, the management instance will be destroyed!");
}
// Clear other members
m_tModuleID = 0;
m_pathModule.clear();
return true;
}
bool CModuleInst::HasActiveObjects() const
{
std::unique_lock<std::recursive_mutex> lock(m_mtxModule);
return m_fnActiveObjects && m_fnActiveObjects();
}
#if defined _WIN32 && defined __GNUC__
#pragma GCC diagnostic pop
#endif

170
sdv_services/core/module.h Normal file
View File

@@ -0,0 +1,170 @@
#ifndef MODULE_H
#define MODULE_H
#include <mutex>
#include <string>
#include <map>
#include <functional>
#include <optional>
#include <interfaces/core.h>
#include <interfaces/module.h>
/**
* @brief Module instance class
*/
class CModuleInst
{
public:
/**
* @brief Constructor
* @param[in] rpathModuleConfigPath Reference to the path of the module as it was specified in the configuration.
* @param[in] rpathModule Reference to the absolute path of the module to load. If empty, the module is not instanceable, but
* is used as a placeholder for rewriting the configuration.
*/
CModuleInst(const std::filesystem::path& rpathModuleConfigPath, const std::filesystem::path& rpathModule) noexcept;
/**
* @brief Copy constructor is not allowed.
*/
CModuleInst(const CModuleInst&) = delete;
/**
* @brief Move constructor is not allowed.
*/
CModuleInst(CModuleInst&&) = delete;
/**
* @brief Destructor
*/
~CModuleInst();
/**
* @brief Assignment operator is not allowed.
* @return Returns a reference to this class.
*/
CModuleInst& operator=(const CModuleInst&) = delete;
/**
* @brief Move operator is not allowed.
* @return Returns a reference to this class.
*/
CModuleInst& operator=(CModuleInst&&) = delete;
/**
* @brief Return whether the module instance was loaded correctly.
* @return Returns 'true' when the module was lloaded successfully. Otherwise returns 'false'.
*/
bool IsValid() const noexcept;
/**
* @brief Get the default object name.
* @param[in] ssClassName Name of the class
* @return Returns the default object name or an empty string if the class doesn't exist.
*/
std::string GetDefaultObjectName(const std::string& ssClassName) const;
/**
* @brief Is the object class marked as singleton (only one instance is allowed)?
* @param[in] ssClassName Name of the class
* @return Returns whether the object class is a singleton.
*/
bool IsSingleton(const std::string& ssClassName) const;
/**
* @brief Gets the set of all available classes in the module
* @returns Returns the vector of all available classes in the module
*/
std::vector<std::string> GetAvailableClasses() const;
/**
* @brief Creates an object instance of a given SDV class via the loaded module
* @param[in] ssClassName the class from which an object is to be created
* @return Returns a pointer to the created object or nullptr in case of failure
*/
sdv::IInterfaceAccess* CreateObject(const std::string& ssClassName);
/**
* @brief Destroys an object previously created via CreateObject
* @param[in] object The object to be destroyed.
* The object is only destroyed if it has been allocated via the loaded module and has not yet been destroyed.
*/
void DestroyObject(sdv::IInterfaceAccess* object);
/**
* @brief Return the module config path (as it was specified in the configuration).
* @return The config path of the module.
*/
std::filesystem::path GetModuleConfigPath() const;
/**
* @brief Return the module path.
* @return The path of the module.
*/
std::filesystem::path GetModulePath() const;
/**
* @brief Return the module ID.
* @return The path of the module.
*/
sdv::core::TModuleID GetModuleID() const;
/**
* @brief Get the module information.
* @return Returns the module information structure.
*/
sdv::core::SModuleInfo GetModuleInfo() const;
/**
* @brief Return the class information of the supplied class name.
* @param[in] rssClassName Reference to string containing the class name.
* @return Returns the information structure of the requested class or an empty structure when the class could not be found.
*/
std::optional<sdv::SClassInfo> GetClassInfo(const std::string& rssClassName) const;
/**
* @brief Load the module.
* @param[in] rpathModule Reference to the path of the module to load.
* @return Returns 'true' when successful; 'false' when not.
*/
bool Load(const std::filesystem::path& rpathModule) noexcept;
/**
* @brief Unload the module and clear the module instance class.
* @attention If there are any active objects still running, this module is not unloaded. In cooperation with the bForce
* flag this causes the module to stay in memory indefinitely.
* @param[in] bForce When set, the module instance class is cleared whether or not the module is unloaded or not. Use with
* extreme care (e.g. during shutdown).
* @return Returns 'true' when successfully cleared the module instance class, 'false' when there are still active objects
* blocking the unloading.
*/
bool Unload(bool bForce) noexcept;
/**
* @brief Return whether active objects created through this module are still running.
* @return Returns 'true' when active objects are running; 'false' when not.
*/
bool HasActiveObjects() const;
private:
/// HasActiveObjects exported function type definition.
using TFNHasActiveObjects = bool();
/// GetModuleFactory exported function type definition.
using TFNGetModuleFactory = sdv::IInterfaceAccess*(uint32_t);
/// GetManifest exported function type definition.
using TFNGetManifest = const char*();
mutable std::recursive_mutex m_mtxModule; ///< Access regulation for the class members.
std::filesystem::path m_pathModuleConfigPath; ///< Module path as it was specified in the configuration.
std::filesystem::path m_pathModule; ///< Module path or filename.
sdv::core::TModuleID m_tModuleID = 0; ///< Module handle.
std::function<TFNHasActiveObjects> m_fnActiveObjects; ///< Module exported function for requesting active objects.
std::function<TFNGetModuleFactory> m_fnGetFactory; ///< Module exported function for getting factory.
std::function<TFNGetManifest> m_fnGetManifest; ///< Module exported function for getting manifest.
sdv::IObjectFactory* m_pFactory = nullptr; ///< Factory interface of the module.
std::map<std::string, sdv::SClassInfo> m_mapClassInfo; ///< Available classes
uint32_t m_uiIfcVersion = 0; ///< Interface version.
};
#endif // !defined MODULE_H

View File

@@ -0,0 +1,411 @@
#include "module_control.h"
#include "sdv_core.h"
#include <algorithm>
#include "../../global/exec_dir_helper.h"
#include "toml_parser/parser_toml.h"
#include "toml_parser//parser_node_toml.h"
/// @cond DOXYGEN_IGNORE
#ifdef _WIN32
#include <Shlobj.h>
#elif defined __unix__
#include <ctime>
#include <utime.h>
#else
#error OS is not supported!
#endif
/// @endcond
CModuleControl::CModuleControl()
{}
CModuleControl::~CModuleControl()
{
try
{
UnloadAll(std::vector<sdv::core::TModuleID>());
}
catch (const sdv::XSysExcept&)
{
}
catch (const std::exception&)
{
}
}
bool CModuleControl::AddModuleSearchDir(const sdv::u8string& ssDir)
{
// Add initial paths if not done so already
AddCurrentPath();
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
std::filesystem::path pathDir(ssDir.c_str());
// Relative paths are always relative to the executable
if (pathDir.is_relative())
pathDir = GetExecDirectory() / ssDir.c_str();
// Remove any indirections
pathDir = pathDir.lexically_normal();
// If the current path is not a directory, it cannot be added
if (!std::filesystem::is_directory(pathDir)) return false;
// Check whether the path is already in the list
if (std::find(m_lstSearchPaths.begin(), m_lstSearchPaths.end(), pathDir) != m_lstSearchPaths.end())
return true; // This is not an error
// Add the path
m_lstSearchPaths.push_back(pathDir);
return true;
}
sdv::sequence<sdv::u8string> CModuleControl::GetModuleSearchDirs() const
{
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
sdv::sequence<sdv::u8string> seqSearchDirs;
for (const std::filesystem::path& rpathSearchDir : m_lstSearchPaths)
seqSearchDirs.push_back(rpathSearchDir.generic_u8string());
return seqSearchDirs;
}
sdv::sequence<sdv::core::SModuleInfo> CModuleControl::GetModuleList() const
{
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
sdv::sequence<sdv::core::SModuleInfo> seqModuleInfos;
// Build the module information sequence
for (const std::shared_ptr<CModuleInst>& ptrModule : m_lstModules)
{
if (!ptrModule) continue;
seqModuleInfos.push_back(ptrModule->GetModuleInfo());
}
return seqModuleInfos;
}
sdv::sequence<sdv::SClassInfo> CModuleControl::GetClassList(/*in*/ sdv::core::TModuleID tModuleID) const
{
// Find the module instance
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
sdv::sequence<sdv::SClassInfo> seqClassInfos;
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
{
return ptrModule && ptrModule->GetModuleID() == tModuleID;
});
if (itModule == m_lstModules.end()) return seqClassInfos; // Cannot find the module.
// Request the class infos from the module instance.
std::vector<std::string> vecClasses = (*itModule)->GetAvailableClasses();
for (const std::string& rssClass : vecClasses)
{
auto optClassInfo = (*itModule)->GetClassInfo(rssClass);
if (!optClassInfo) continue;
seqClassInfos.push_back(*optClassInfo);
}
return seqClassInfos;
}
sdv::core::TModuleID CModuleControl::Load(const sdv::u8string& ssModulePath)
{
if(ssModulePath.empty())
return 0;
// Add initial paths if not done so already
AddCurrentPath();
// Core services bypass.
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
std::filesystem::path pathModule;
if (ssModulePath == "core_services.sdv")
pathModule = static_cast<std::string>(ssModulePath);
else
{
if (GetAppControl().IsMainApplication() || GetAppControl().IsIsolatedApplication())
{
// Check the installation for the module.
pathModule = GetAppConfig().FindInstalledModule(static_cast<std::string>(ssModulePath));
}
else
{
try
{
// Get the search paths if the module path is relative
std::list<std::filesystem::path> lstSearchPaths;
std::filesystem::path pathSupplied(static_cast<std::string>(ssModulePath));
if (pathSupplied.is_relative())
lstSearchPaths = m_lstSearchPaths;
// Add an empty path to allow the OS to search when our own search paths could not find the module.
lstSearchPaths.push_back(std::filesystem::path());
// Run through the search paths and try to find the module.
for (const std::filesystem::path& rpathDirectory : lstSearchPaths)
{
// Compose the path
std::filesystem::path pathModuleTemp;
if (rpathDirectory.is_absolute())
pathModuleTemp = (rpathDirectory / pathSupplied).lexically_normal();
else
{
if (rpathDirectory.empty())
pathModuleTemp = pathSupplied.lexically_normal();
else
pathModuleTemp = (GetExecDirectory() / rpathDirectory / pathSupplied).lexically_normal();
}
if (std::filesystem::exists(pathModuleTemp))
{
pathModule = pathModuleTemp;
break;
}
}
}
catch (const std::exception& re)
{
SDV_LOG_ERROR("Supplied path to module load is not valid \"", ssModulePath, "\": ", re.what());
}
}
}
// Check for an exisiting module. If existing, return the existing module.
for (const std::shared_ptr<CModuleInst>& rptrModule : m_lstModules)
{
if (rptrModule && rptrModule->GetModulePath() == pathModule)
return rptrModule->GetModuleID();
}
// Create a new instance (even if the module could not be found).
std::shared_ptr<CModuleInst> ptrModule = std::make_shared<CModuleInst>(static_cast<std::string>(ssModulePath), pathModule);
m_lstModules.push_back(ptrModule);
m_setConfigModules.insert(ptrModule->GetModuleID());
if (!ptrModule->IsValid())
{
SDV_LOG_ERROR("The module was not found \"", ssModulePath, "\"");
return 0; // Do not return the module ID of not loadable modules.
}
return ptrModule->GetModuleID();
}
bool CModuleControl::Unload(sdv::core::TModuleID tModuleID)
{
// Cannot unload the core services
if (tModuleID == sdv::core::tCoreLibModule)
return true;
return ContextUnload(tModuleID, false);
}
bool CModuleControl::HasActiveObjects(sdv::core::TModuleID tModuleID) const
{
// Find the module instance
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
{
return ptrModule && ptrModule->GetModuleID() == tModuleID;
});
if (itModule == m_lstModules.end()) return false; // Cannot find the module; no active objects known.
// Redirect the call to the module instance.
return (*itModule)->HasActiveObjects();
}
std::shared_ptr<CModuleInst> CModuleControl::FindModuleByClass(const std::string& rssClass)
{
// Find the module instance
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
for (const std::shared_ptr<CModuleInst>& ptrModule : m_lstModules)
{
if (!ptrModule) continue;
std::vector<std::string> vecClasses = ptrModule->GetAvailableClasses();
for (const std::string& rssClassLocal : vecClasses)
{
if (rssClassLocal == rssClass) return ptrModule;
auto optClassInfo = ptrModule->GetClassInfo(rssClass);
if (!optClassInfo) continue;
if (std::find(optClassInfo->seqClassAliases.begin(), optClassInfo->seqClassAliases.end(), rssClass) !=
optClassInfo->seqClassAliases.end())
return ptrModule;
}
}
// For main and isolated applications, check whether the module is in one of the installation manifests.
auto optManifest = GetAppConfig().FindInstalledComponent(rssClass);
if (!optManifest) return nullptr;
auto ssManifest = GetAppConfig().FindInstalledModuleManifest(optManifest->pathRelModule);
if (ssManifest.empty()) return nullptr;
lock.unlock();
// Load the module
return GetModule(ContextLoad(optManifest->pathRelModule, ssManifest));
}
std::shared_ptr<CModuleInst> CModuleControl::GetModule(sdv::core::TModuleID tModuleID) const
{
// Find the module instance
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
{
return ptrModule && ptrModule->GetModuleID() == tModuleID;
});
if (itModule == m_lstModules.end()) return nullptr; // Cannot find the module.
return *itModule;
}
void CModuleControl::UnloadAll(const std::vector<sdv::core::TModuleID>& rvecIgnoreModules)
{
// Force unloading all modules in reverse order of their loading
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
std::list<std::shared_ptr<CModuleInst>> lstCopy = m_lstModules;
lock.unlock();
while (lstCopy.size())
{
std::shared_ptr<CModuleInst> ptrModule = std::move(lstCopy.back());
lstCopy.pop_back();
if (!ptrModule) continue;
// On the ignore list?
if (std::find(rvecIgnoreModules.begin(), rvecIgnoreModules.end(), ptrModule->GetModuleID()) != rvecIgnoreModules.end())
continue;
// Unload... -> call the module control function to remove it from the list.
ContextUnload(ptrModule->GetModuleID(), true);
}
}
void CModuleControl::ResetConfigBaseline()
{
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
m_setConfigModules.clear();
}
std::string CModuleControl::SaveConfig(const std::set<std::filesystem::path>& rsetIgnoreModule)
{
std::stringstream sstream;
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
// Add all the loaded modules
for (const std::shared_ptr<CModuleInst>& rptrModule : m_lstModules)
{
if (m_setConfigModules.find(rptrModule->GetModuleID()) != m_setConfigModules.end() &&
rsetIgnoreModule.find(rptrModule->GetModuleConfigPath()) != rsetIgnoreModule.end())
{
sstream << std::endl;
sstream << "[[Module]]" << std::endl;
sstream << "Path = \"" << rptrModule->GetModuleConfigPath().generic_u8string() << "\"" << std::endl;
}
}
return sstream.str();
}
sdv::core::TModuleID CModuleControl::ContextLoad(const std::filesystem::path& rpathModule, const std::string& rssManifest)
{
if (GetAppControl().IsMaintenanceApplication()) return 0; // Not allowed
// Run through the manifest and check for complex services, applications and utilities.
// TODO EVE: Temporary suppression of cppcheck warning.
// cppcheck-suppress variableScope
[[maybe_unused]] bool bIsolationNeeded = false;
try
{
size_t nIndex = 0;
size_t nUnsupportedObjectCount = 0;
CParserTOML parser(rssManifest);
do
{
std::shared_ptr<CNode> ptrComponentType =
parser.GetRoot().GetDirect(std::string("Component[") + std::to_string(nIndex++) + "].Type");
if (!ptrComponentType) break;
std::string ssType = static_cast<std::string>(ptrComponentType->GetValue());
if (ssType == "Device") continue; // Okay
if (ssType == "BasicService") continue; // Okay
if (ssType == "Proxy") continue; // Okay
if (ssType == "Stub") continue; // Okay
if (ssType == "ComplexService" || ssType == "Application" || ssType == "Utility")
{
bIsolationNeeded = true;
continue; // Okay
}
// Not okay
nUnsupportedObjectCount++;
} while (true);
if (nIndex == nUnsupportedObjectCount) return 0; // No object to load.
}
catch (const sdv::toml::XTOMLParseException& /*rexcept*/)
{
return 0;
}
// TODO: If the module to load needs isolation...
// If this is a main application: isolate modules that should be isolated
// If this is an isolated application: check for another loaded module that needs isolation already. If this do not allow
// this module to load.
// Load the module
return Load(rpathModule.generic_u8string());
}
bool CModuleControl::ContextUnload(sdv::core::TModuleID tModuleID, bool bForce)
{
// Find the module instance
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
{
return ptrModule && ptrModule->GetModuleID() == tModuleID;
});
if (itModule == m_lstModules.end()) return true; // Cannot find the module to unload. This is not an error!
m_setConfigModules.erase(tModuleID); // Remove from config if part of it.
// Check whether it is possible to unload.
bool bSuccess = (*itModule)->Unload(bForce);
if (bSuccess)
m_lstModules.erase(itModule);
return bSuccess;
}
std::shared_ptr<CModuleInst> CModuleControl::FindModuleByPath(const std::filesystem::path& rpathModule) const
{
// Find the module instance
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
auto itModule = std::find_if(m_lstModules.begin(), m_lstModules.end(), [&](const std::shared_ptr<CModuleInst>& ptrModule)
{
return ptrModule && ptrModule->GetModulePath() == rpathModule;
});
if (itModule == m_lstModules.end()) return nullptr; // Cannot find the module
return *itModule;
}
void CModuleControl::AddCurrentPath()
{
std::unique_lock<std::recursive_mutex> lock(m_mtxModules);
// Do this only once.
if (!m_lstSearchPaths.empty()) return;
// Add the core directory
std::filesystem::path pathCoreDir = GetCoreDirectory().lexically_normal();
m_lstSearchPaths.push_back(pathCoreDir);
// Add the exe dir
std::filesystem::path pathExeDir = GetExecDirectory().lexically_normal();
if (pathExeDir != pathCoreDir) m_lstSearchPaths.push_back(pathExeDir);
}
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
CModuleControl& CModuleControlService::GetModuleControl()
{
return ::GetModuleControl();
}
bool CModuleControlService::EnableModuleControlAccess()
{
return GetAppControl().IsStandaloneApplication() ||
GetAppControl().IsEssentialApplication();
}
#endif

View File

@@ -0,0 +1,265 @@
/**
* @file module_control.h
* @author Sudipta Babu Durjoy FRD DISDS1 (mailto:sudipta.durjoy@zf.com) & Erik Verhoeven FRD DISDS1 (mailto:erik.verhoeven@zf.com)
* @brief This file contains the implementation for loading VAPI/SDV modules and accessing the exported functions related to
* VAPI/SDV modules on Windows and Posix
* @version 2.0
* @date 2024-04-03
*
* @copyright Copyright ZF Friedrichshafen AG (c) 2021-2025
*
*/
#ifndef MODULE_CONTROL_H
#define MODULE_CONTROL_H
#include <support/interface_ptr.h>
#include <support/local_service_access.h>
#include <support/component_impl.h>
#include <mutex>
#include <list>
#include <set>
#include "toml_parser/parser_toml.h"
#include "module.h"
/**
* @brief Module control class
* @remarks This class is also used to load the object from the core library without reloading the library itself again. It uses
* the module file name "core_services.sdv" and the ID -1 to identify the library.
*/
class CModuleControl : public sdv::IInterfaceAccess, public sdv::core::IModuleControl, public sdv::core::IModuleControlConfig,
public sdv::core::IModuleInfo
{
public:
/**
* @brief Default constructor
*/
CModuleControl();
/**
* @brief No copy constructor
* @param[in] rctrl Reference to the module control class.
*/
CModuleControl(const CModuleControl& rctrl) = delete;
/**
* @brief No move constructor
* @param[in] rctrl Reference to the module control class.
*/
CModuleControl(CModuleControl&& rctrl) = delete;
/**
* @brief Destructor unloads all modules
*/
virtual ~CModuleControl();
// Interface map
// NOTE: Only the sdv::core::IModuleControlConfig interface should be accessible from here, since the module control is
// accessible through sdv::GetCore() function, which should not make all other interfaces accessible (the system needs to start
// and access should happen over sdv::core::GetObject()).
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::core::IModuleControlConfig)
END_SDV_INTERFACE_MAP()
/**
* @brief No assignment operator
* @return Returns a reference to this class.
*/
CModuleControl& operator=(const CModuleControl&) = delete;
/**
* @brief No move operator
* @return Returns a reference to this class.
*/
CModuleControl& operator=(CModuleControl&&) = delete;
/**
* @brief Sets a search path to a folder where a module can be found. Overload of
* sdv::core::IModuleControlConfig::AddModuleSearchDir.
* @param[in] ssDir Relative or absolute path to an existing folder.
* To load a module out of the folder only the filename is required.
* @return returns true if folder exists, otherwise false
*/
virtual bool AddModuleSearchDir(const sdv::u8string& ssDir) override;
/**
* @brief Get a sequence containing the current module search directories.
* @return The sequence with the module search directories.
*/
virtual sdv::sequence<sdv::u8string> GetModuleSearchDirs() const override;
/**
* @brief Get a list of loaded modules. Overload of sdv::core::IModuleInfo::GetModuleList.
* @return The module list.
*/
virtual sdv::sequence<sdv::core::SModuleInfo> GetModuleList() const override;
/**
* @brief Get a list of classes exposed by the provided module.
* @param[in] tModuleID The module ID to request the list of classes for.
* @return Sequence with the class information structures.
*/
virtual sdv::sequence<sdv::SClassInfo> GetClassList(/*in*/ sdv::core::TModuleID tModuleID) const override;
/**
* @brief Load the module. Overload of sdv::core::IModuleControl::Load.
* @param[in] ssModulePath File name of the module to load.
* @return Returns the ID of the module or 0 if the module could not be loaded
*/
virtual sdv::core::TModuleID Load(const sdv::u8string& ssModulePath) override;
/**
* @brief Unload the module. Overload of sdv::core::IModuleControl::Unload.
* @param[in] tModuleID Id representing a module, which has previous been loaded by Load
* @return Returns true if module is unloaded successfully otherwise false.
*/
virtual bool Unload(sdv::core::TModuleID tModuleID) override;
/**
* @brief Checking for active objects. Overload of sdv::core::IModuleControl::HasActiveObjects.
* @param[in] tModuleID Id representing a module, which has previous been loaded by Load
* @return Returns true if there is an active object otherwise false.
*/
virtual bool HasActiveObjects(sdv::core::TModuleID tModuleID) const override;
/**
* @brief Find a module containing a class with the specified name.
* @details Find the first module containing a class with the specified name. For main and isolated applications, if the
* module wasn't loaded, the installation manifests are checked and if a module with the class has been found, the module will
* be loaded. The order of checking the installation manifest is core-manifest, manifest in executable directory and manifest
* in supplied installation directory.
* @remarks Modules of system objects specified in the user installation are not returned.
* @param[in] rssClass Reference to the class that should be searched for. The class is compared to the class name and the
* default name in the manifest.
* @return Returns a smart pointer to the module instance or NULL when the module hasn't been found.
*/
std::shared_ptr<CModuleInst> FindModuleByClass(const std::string& rssClass);
/**
* @brief Get a class instance for the module.
* @param[in] tModuleID Id representing a module, which has previous been loaded by Load
* @return Returns a smart pointer to the module instance or NULL when the module hasn't been found.
*/
std::shared_ptr<CModuleInst> GetModule(sdv::core::TModuleID tModuleID) const;
/**
* @brief Unload all modules (with force-flag).
* @param[in] rvecIgnoreModules Reference to a vector containing all the modules to not unload.
*/
void UnloadAll(const std::vector<sdv::core::TModuleID>& rvecIgnoreModules);
/**
* @brief Reset the current config baseline.
*/
void ResetConfigBaseline();
/**
* @brief Save the configuration of all modules.
* @param[in] rsetIgnoreModule Set of modules not needing to add.
* @return The string containing all the modules.
*/
std::string SaveConfig(const std::set<std::filesystem::path>& rsetIgnoreModule);
/**
* @brief Load the module with the supplied module manifest. Only for main and isolated applications.
* @remarks When running the main application all complex services, applications and utilities run in their isolated
* environment. If multiple components are implemented in the module, of which one is suppose to run isolated, all of them run
* isolated. Isolated applications cannot load other modules if one was loaded already. Maintenance applications cannot load
* modules. All other applications load modules in their own process. Exception to the rul are proxy and stub components.
* System objects cannot be loaded this way at all.
* @param[in] rpathModule Absolute file path of the module to load.
* @param[in] rssManifest The module manifest deciding on where the module should run.
* @return Returns the ID of the module or 0 if the module could not be loaded
*/
sdv::core::TModuleID ContextLoad(const std::filesystem::path& rpathModule, const std::string& rssManifest);
/**
* @brief Unload the module.
* @param[in] tModuleID Id representing a module, which has previous been loaded by ContextLoad.
* @param[in] bForce When set, the module instance class is cleared whether or not the module is unloaded or not. Use with
* extreme care (e.g. during shutdown).
* @return Returns true if module is unloaded successfully otherwise false.
*/
bool ContextUnload(sdv::core::TModuleID tModuleID, bool bForce);
private:
/**
* @brief Find a module by its path.
* @param[in] rpathModule File path of the module to load. Relative paths are relative to the installation directory for main
* and isolated applications and relative to the exe for all other applications.
* @return Returns a smart pointer to the module instance or NULL when the module hasn't been found (wasn't loaded).
*/
std::shared_ptr<CModuleInst> FindModuleByPath(const std::filesystem::path& rpathModule) const;
/**
* @brief HasActiveObjects exported function type definition.
*/
using TFNHasActiveObjects = bool();
/**
* @brief GetModuleFactory exported function type definition.
*/
using TFNGetModuleFactory = sdv::IInterfaceAccess*(uint32_t);
/**
* @brief Add core_services and executable folders to the search paths if not done so already.
*/
void AddCurrentPath();
using TConfigSet = std::set<sdv::core::TModuleID>;
mutable std::recursive_mutex m_mtxModules; ///< Control of the loaded library access
std::list<std::shared_ptr<CModuleInst>> m_lstModules; ///< List of modules. The order of loading is preserved.
std::list<std::filesystem::path> m_lstSearchPaths; ///< List of search directories.
TConfigSet m_setConfigModules; ///< Set with the modules for storing in the configuration.
};
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
/**
* @brief Module control service
*/
class CModuleControlService : public sdv::CSdvObject
{
public:
CModuleControlService() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_CHAIN_MEMBER(GetModuleControl())
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IModuleInfo, GetModuleControl())
SDV_INTERFACE_SET_SECTION_CONDITION(EnableModuleControlAccess(), 1)
SDV_INTERFACE_SECTION(1)
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IModuleControl, GetModuleControl())
SDV_INTERFACE_DEFAULT_SECTION()
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_NAME("ModuleControlService")
DECLARE_OBJECT_SINGLETON()
/**
* @brief Get access to the module control
* @return Returns the one global instance of the module control.
*/
static CModuleControl& GetModuleControl();
/**
* @brief When set, the module control will be enabled.
* @return Returns whether access to the module control is granted.
*/
static bool EnableModuleControlAccess();
};
DEFINE_SDV_OBJECT_NO_EXPORT(CModuleControlService)
#endif
#ifdef _WIN32
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#endif
#endif // !define MODULE_CONTROL_H

View File

@@ -0,0 +1,43 @@
#include "object_lifetime_control.h"
#include "sdv_core.h"
CObjectLifetimeControl::CObjectLifetimeControl(sdv::IInterfaceAccess* pObject, IObjectDestroyHandler& rHandler,
bool bAutoDestroy /*= true*/) : m_rHandler(rHandler), m_ptrObject(pObject), m_bAutoDestroy(bAutoDestroy)
{
m_pObjectDestroy = m_ptrObject.GetInterface<sdv::IObjectDestroy>();
m_pObjectLifetime = m_ptrObject.GetInterface<sdv::IObjectLifetime>();
}
void CObjectLifetimeControl::DestroyObject()
{
if (m_pObjectDestroy)
m_pObjectDestroy->DestroyObject();
else if (m_pObjectLifetime)
m_pObjectLifetime->Decrement();
m_rHandler.OnDestroyObject(m_ptrObject);
if (m_bAutoDestroy) delete this;
}
void CObjectLifetimeControl::Increment()
{
if (m_pObjectLifetime) m_pObjectLifetime->Increment();
}
bool CObjectLifetimeControl::Decrement()
{
bool bRet = false;
if (m_pObjectLifetime) bRet = m_pObjectLifetime->Decrement();
if (bRet)
{
m_rHandler.OnDestroyObject(m_ptrObject);
if (m_bAutoDestroy) delete this;
}
return bRet;
}
uint32_t CObjectLifetimeControl::GetCount() const
{
return m_pObjectLifetime ? m_pObjectLifetime->GetCount() : 0u;
}

View File

@@ -0,0 +1,87 @@
#ifndef OBJECT_LIFETIME_CONTROL_H
#define OBJECT_LIFETIME_CONTROL_H
#include <support/interface_ptr.h>
/**
* @brief Object destroy handler - callback interface
*/
interface IObjectDestroyHandler
{
/**
* @brief Inform about an object destruction. This function might delete the calling class, so no other function must be
* called after the call to this function.
* @param[in] pObject Pointer to the object being destroyed.
*/
virtual void OnDestroyObject(sdv::IInterfaceAccess* pObject) = 0;
};
/**
* @brief Wrapper class around the object to allow objects to be destroyed through sdv::IObjectDestroy.
*/
class CObjectLifetimeControl : public sdv::IInterfaceAccess, public sdv::IObjectDestroy, public sdv::IObjectLifetime
{
public:
/**
* @brief Constructor assigning the object instance.
* @param[in] pObject Interface pointer to the object instance. Must not be nullptr.
* @param[in] rHandler Reference to the handler being informed about the object destruction.
* @param[in] bAutoDestroy When set, the wrapper class will be destroyed automatically when the object is destroyed.
*/
CObjectLifetimeControl(sdv::IInterfaceAccess* pObject, IObjectDestroyHandler& rHandler, bool bAutoDestroy = true);
/**
* @brief Virtual destructor
*/
virtual ~CObjectLifetimeControl() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_SET_SECTION_CONDITION(!m_pObjectLifetime, 1)
SDV_INTERFACE_SECTION(1)
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
SDV_INTERFACE_DEFAULT_SECTION()
SDV_INTERFACE_SET_SECTION_CONDITION(m_pObjectLifetime, 2)
SDV_INTERFACE_SECTION(2)
SDV_INTERFACE_ENTRY(sdv::IObjectLifetime)
SDV_INTERFACE_DEFAULT_SECTION()
SDV_INTERFACE_DENY_ENTRY(sdv::IObjectControl) // Do not allow users to directly access the object control.
SDV_INTERFACE_CHAIN_MEMBER(m_ptrObject)
END_SDV_INTERFACE_MAP()
/**
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
* @attention After a call of this function, all exposed interfaces render invalid and should not be used any more.
*/
virtual void DestroyObject() override;
/**
* @brief Increment the lifetime. Needs to be balanced by a call to Decrement. Overload of sdv::IObjectLifetime::Increment.
*/
virtual void Increment() override;
/**
* @brief Decrement the lifetime. If the lifetime reaches zero, the object will be destroyed (through the exposed
* IObjectDestroy interface). Overload of sdv::IObjectLifetime::Decrement.
* @return Returns 'true' if the object was destroyed, false if not.
*/
virtual bool Decrement() override;
/**
* @brief Get the current lifetime count. Overload of sdv::IObjectLifetime::GetCount.
* @remarks The GetCount function returns a momentary value, which can be changed at any moment.
* @return Returns the current counter value.
*/
virtual uint32_t GetCount() const override;
private:
IObjectDestroyHandler& m_rHandler; ///< Reference to the handler class being informed about the
///< destruction.
sdv::IObjectDestroy* m_pObjectDestroy = nullptr; ///< IObjectDestroy interface is exposed by object
sdv::IObjectLifetime* m_pObjectLifetime = nullptr; ///< IObjectLifetime interface is exposed by object
sdv::TInterfaceAccessPtr m_ptrObject; ///< Interface pointer to the object.
bool m_bAutoDestroy = false; ///< When set, the lifetime wrapper object is destroyed after an
///< object destroy call.
};
#endif // !defined OBJECT_LIFETIME_CONTROL_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,397 @@
#ifndef VAPI_REPOSITORY_H
#define VAPI_REPOSITORY_H
#include <list>
#include <map>
#include <mutex>
#include <string>
#include <set>
#include <shared_mutex>
#include <interfaces/repository.h>
#include <support/component_impl.h>
#include <condition_variable>
#include "module.h"
#include "object_lifetime_control.h"
#include "iso_monitor.h"
/**
* @brief repository service providing functionality to load modules, create objects and access exiting objects
*/
class CRepository :
public sdv::IInterfaceAccess, public sdv::core::IObjectAccess, public sdv::core::IRepositoryUtilityCreate,
public sdv::core::IRepositoryMarshallCreate, public sdv::core::IRepositoryControl, public sdv::core::IRegisterForeignObject,
public sdv::core::IRepositoryInfo, public IObjectDestroyHandler, public sdv::core::ILinkCoreRepository
{
public:
/**
* @brief Default constructor
*/
CRepository() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::core::IObjectAccess)
SDV_INTERFACE_ENTRY(sdv::core::IRepositoryMarshallCreate)
SDV_INTERFACE_ENTRY(sdv::core::IRepositoryUtilityCreate)
END_SDV_INTERFACE_MAP()
/**
* @brief Switch all components in config mode.
*/
void SetConfigMode();
/**
* @brief Switch all components in running mode.
*/
void SetRunningMode();
/**
* @brief Get the object instance previously created through the repository service. Overload of IObjectAccess::GetObject.
* @param[in] ssObjectName The name of the requested object.
* @return Returns the IInterfaceAccess interface of the object instance if found and nullptr otherwise.
*/
virtual sdv::IInterfaceAccess* GetObject(/*in*/ const sdv::u8string& ssObjectName) override;
/**
* @brief Get the object instance previously created through the repository service.
* @attention Utilities cannot be returned by this function.
* @param[in] tObjectID The ID of the requested object.
* @return Returns the IInterfaceAccess interface of the object instance if found and nullptr otherwise.
*/
virtual sdv::IInterfaceAccess* GetObjectByID(/*in*/ sdv::core::TObjectID tObjectID) override;
/**
* @brief Creates an utility object from a previously loaded module. Overload of IRepositoryUtilityCreate::CreateUtility.
* @attention Utilities are standalone objects. Use IObjectDestroy to destroy the utility.
* @param[in] ssClassName The name of the object class to be created.
* @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl.
* @return Returns the IInterfaceAccess interface of newly created utility.
*/
virtual sdv::IInterfaceAccess* CreateUtility(/*in*/ const sdv::u8string& ssClassName,
/*in*/ const sdv::u8string& ssObjectConfig) override;
/**
* @brief Create a proxy object for the interface with the supplied ID. If successful, this object is initialized, but
* not linked to any other object within the system. Overload of sdv::core::IRepositoryMarshallCreate::CreateProxyObject.
* @details Create a proxy object with the name "proxy_<ifc id>". "ifc_id" is the decimal value of an interface ID.
* @param[in] id The interface ID to create the object for.
* @return Returns the interface to the proxy object. Destruction of the object can be achieved through IObjectDestroy.
*/
virtual sdv::IInterfaceAccess* CreateProxyObject(/*in*/ sdv::interface_id id) override;
/**
* @brief Create a stub object for the interface with the supplied ID. If successful, this object is initialized, but
* not linked to any other object within the system. Overload of sdv::core::IRepositoryMarshallCreate::CreateStubObject.
* @details Create a stub object with the name "stub_<ifc id>". "ifc_id" is the decimal value of an interface ID.
* @param[in] id The interface ID to create the object for.
* @return Returns the interface to the stub object. Destruction of the object can be achieved through IObjectDestroy.
*/
virtual sdv::IInterfaceAccess* CreateStubObject(/*in*/ sdv::interface_id id) override;
protected:
/**
* @brief Create an object and all its objects it depends on. Overload of Overload of
* sdv::core::IRepositoryControl::CreateObject.
* @details For standalone and essential applications, this function allows the creation of system, device and service
* objects if the module was loaded previously. For the main and isolated application, this function allows the
* creation of complex services only and only those that are in the installation. For the isolated application only one
* complex service can be created. External apps, utilities, and proxy and stub objects cannot be created at all using
* this function.
* Objects that the to be create object is depending on will be created as well. For the main application this is
* limited to complex services. Isolated applications cannot load other services; this is taken over by the main
* application.
* @param[in] ssClassName The name of the object class to be created. For the main application, the class string could
* be empty for the main application if the object was defined in the installation.
* @param[in] ssObjectName Name of the object, required to be unique. For standalone and essential applications, the
* name string can be empty, in which case the object might either provide a name proposal or the name is the same as
* the class name. Use the returned object ID to request the name of the object.
* @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl. Only
* valid for standalone, essential and isolated applications.
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
* the object ID of the existing object is returned.
*/
virtual sdv::core::TObjectID CreateObject(/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName,
/*in*/ const sdv::u8string& ssObjectConfig) override;
public:
/**
* @brief Creates an object from a previously loaded module. Provide the module ID to explicitly define what module to
* use during object creation. Overload of sdv::core::IRepositoryControl::CreateObjectFromModule.
* @param[in] tModuleID Module ID that contains the object class to create the object with.
* @param[in] ssClassName The name of the object class to be created.
* @param[in] ssObjectName Optional name of the object - required to be unique. If not supplied, the object might
* either provide a name proposal or the name is the same as the class name. Use the returned object ID to request
* the name of the object.
* @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl.
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
* the object ID of the existing object is returned.
*/
virtual sdv::core::TObjectID CreateObjectFromModule(/*in*/ sdv::core::TModuleID tModuleID,
/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName,
/*in*/ const sdv::u8string& ssObjectConfig) override;
protected:
/**
* @brief Destroy a previously created object with the supplied name. Overload of sdv::core::IRepositoryControl::DestroyObject.
* @details For standalone and essential applications previously created system, device and service objects can be
* destroyed. For the main and isolated applications, only the complex service can be destroyed. For isolated
* applications a destruction of the object will end the application.
* @param[in] ssObjectName The name of the object to destroy.
* @return Returns whether the object destruction was successful.
*/
virtual bool DestroyObject(/*in*/ const sdv::u8string& ssObjectName) override;
public:
/**
* @brief Create an object and all its objects it depends on. Internal function not accessible through the interface.
* @param[in] ssClassName The name of the object class to be created. For the main application, the class string could
* be empty for the main application if the object was defined in the installation.
* @param[in] ssObjectName Name of the object, required to be unique. For standalone and essential applications, the
* name string can be empty, in which case the object might either provide a name proposal or the name is the same as
* the class name. Use the returned object ID to request the name of the object.
* @param[in] ssObjectConfig Optional configuration handed over to the object upon creation via IObjectControl. Only
* valid for standalone, essential and isolated applications.
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
* the object ID of the existing object is returned.
*/
sdv::core::TObjectID CreateObject2(/*in*/ const sdv::u8string& ssClassName, /*in*/ const sdv::u8string& ssObjectName,
/*in*/ const sdv::u8string& ssObjectConfig);
/**
* @brief Destroy a previously created object with the supplied name. Internal function not accessible through the interface.
* @details For standalone and essential applications previously created system, device and service objects can be
* destroyed. For the main and isolated applications, only the complex service can be destroyed. For isolated
* applications a destruction of the object will end the application.
* @param[in] ssObjectName The name of the object to destroy.
* @return Returns whether the object destruction was successful.
*/
bool DestroyObject2(/*in*/ const sdv::u8string& ssObjectName);
/**
* @brief Register as foreign object and make it public to the system with the given name. Overload of
* sdv::core::IRegisterForeignObject::RegisterObject.
* @param[in] pObjectIfc Interface of the object to be registered.
* @param[in] ssObjectName The name under which the object - required to be unique.
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
* the object ID of the existing object is returned.
*/
virtual sdv::core::TObjectID RegisterObject(/*in*/ sdv::IInterfaceAccess* pObjectIfc,
/*in*/ const sdv::u8string& ssObjectName) override;
/**
* @brief Register the core repository.
* @param[in] pCoreRepository Pointer to the proxy interface of the core repository.
*/
virtual void LinkCoreRepository(/*in*/ sdv::IInterfaceAccess* pCoreRepository) override;
/**
* @brief Unlink a previously linked core repository.
*/
virtual void UnlinkCoreRepository() override;
/**
* @brief Find the class information of an object with the supplied name. Overload of
* sdv::core::IRepositoryControl::IRepositoryInfo::FindClass.
* @param[in] ssClassName Object class name.
* @return The object class information.
*/
virtual sdv::SClassInfo FindClass(/*in*/ const sdv::u8string& ssClassName) const override;
/**
* @brief Get a list of all the instantiated objects. Overload of sdv::core::IRepositoryControl::IRepositoryInfo::GetObjectList.
* @return Sequence containing the object information structures.
*/
virtual sdv::sequence<sdv::core::SObjectInfo> GetObjectList() const override;
/**
* @brief Get the object info for the requested object. Overload of sdv::core::IRepositoryInfo::GetObjectInfo.
* @param[in] tObjectID The object ID to return the object information for.
* @return The object information structure if the object is available or an empty structure if not.
*/
virtual sdv::core::SObjectInfo GetObjectInfo(/*in*/ sdv::core::TObjectID tObjectID) const override;
/**
* @brief Find an object with the supplied name. Only object instances that are in the service list can be found with this
* function (devices, basic and complex services, and system objects). Overload of sdv::core::IRepositoryInfo::FindObject.
* @param[in] ssObjectName Object name to search for.
* @return The object information structure if the object is available or an empty structure if not.
*/
virtual sdv::core::SObjectInfo FindObject(/*in*/ const sdv::u8string& ssObjectName) const override;
/**
* @brief Remove an object instance from the local object map. Overload of IObjectDestroyHandler::OnDestroyObject.
* @param[in] pObject Interface pointer to the object instance.
*/
virtual void OnDestroyObject(sdv::IInterfaceAccess* pObject) override;
/**
* @brief Destroy all objects from the service map created for a specific module.
* @param[in] tModuleID Module ID that contains the object class to destroy.
*/
void DestroyModuleObjects(sdv::core::TModuleID tModuleID);
/**
* @brief Destroy all objects from the service map.
* @remarks In Emergency, the objects should be removed without unloading to prevent any more calls to take place. The reason
* is, that the system could do an emergency shutdown and part of the system might have been cleaned up already and part not.
* @param[in] rvecIgnoreObjects Reference to the vector of objects to not destroy.
* @param[in] bForce Force destruction (remove without calling any more function).
*/
void DestroyAllObjects(const std::vector<std::string>&rvecIgnoreObjects, bool bForce = false);
/**
* @brief Reset the current config baseline.
*/
void ResetConfigBaseline();
/**
* @brief Save the configuration of all components.
* @return The string containing all the components.
*/
std::string SaveConfig();
private:
/**
* @brief Create an isolated object (object running in a separate process). Only allowed to be called by the main application.
* @param[in] rsClassInfo Reference to the class information structure of the object.
* @param[in] rssObjectName Reference to the name of the object - required to be unique.
* @param[in] rssObjectConfig Reference to an optional configuration handed over to the object upon creation via IObjectControl.
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
* the object ID of the existing object is returned.
*/
sdv::core::TObjectID CreateIsolatedObject(const sdv::SClassInfo& rsClassInfo, const sdv::u8string& rssObjectName,
const sdv::u8string& rssObjectConfig);
/**
* @brief Creates an object from the supplied module instance.
* @param[in] rptrModule Reference to the module that contains the object class to create the object with.
* @param[in] rsClassInfo Reference to the class information structure of the object.
* @param[in] rssObjectName Reference to the name of the object - required to be unique.
* @param[in] rssObjectConfig Reference to an optional configuration handed over to the object upon creation via IObjectControl.
* @return Returns the object ID when the object creation was successful or 0 when not. On success the object is
* available through the IObjectAccess interface. If the object already exists (class and object names are identical),
* the object ID of the existing object is returned.
*/
sdv::core::TObjectID InternalCreateObject(const std::shared_ptr<CModuleInst>& rptrModule, const sdv::SClassInfo& rsClassInfo,
const sdv::u8string& rssObjectName, const sdv::u8string& rssObjectConfig);
/**
* @brief Create a new unique object ID.
* @return The created object ID.
*/
static sdv::core::TObjectID CreateObjectID();
/**
* @brief Get a list of depending object instances of a specific class.
* @param[in] rssClass Reference to the class name.
* @return Returns a list of object instances.
*/
std::vector<sdv::core::TObjectID> GetDependingObjectInstancesByClass(const std::string& rssClass);
/**
* @brief Object entry
* @details The object instance information. Objects that are running remotely are represented by their proxy. Objects can have
* multiple references by stubs.
*/
struct SObjectEntry
{
sdv::core::TObjectID tObjectID = 0; ///< Object ID (local to this process).
sdv::SClassInfo sClassInfo; ///< Object class name.
std::string ssName; ///< Object name (can be zero with local objects).
std::string ssConfig; ///< Object configuration.
sdv::TInterfaceAccessPtr ptrObject; ///< Object interface (could be proxy).
std::shared_ptr<CModuleInst> ptrModule; ///< Module instance.
bool bControlled = false; ///< When set, the object is controlled.
bool bIsolated = false; ///< When set, the object is isolated and running in another process.
std::mutex mtxConnect; ///< Mutex used to wait for connection
std::condition_variable cvConnect; ///< Condition variable used to wait for connection
std::shared_ptr<CIsoMonitor> ptrIsoMon; ///< Object being monitored for shutdown.
};
using TObjectMap = std::map<sdv::core::TObjectID, std::shared_ptr<SObjectEntry>>;
using TOrderedObjectList = std::list<std::shared_ptr<SObjectEntry>>;
using TObjectIDList = std::list<std::shared_ptr<SObjectEntry>>;
using TServiceMap = std::map<std::string, TOrderedObjectList::iterator>;
using TIsolationMap = std::map<std::string, std::shared_ptr<SObjectEntry>>;
using TLocalObjectMap = std::map<sdv::IInterfaceAccess*, std::shared_ptr<SObjectEntry>>;
using TConfigSet = std::set<sdv::core::TObjectID>;
mutable std::shared_mutex m_mtxObjects; ///< Protects against concurrent access.
TOrderedObjectList m_lstOrderedServiceObjects; ///< List of service object IDs in order of creation.
TServiceMap m_mapServiceObjects; ///< Map of service objects indexed by object name an pointing
///< to the entry in the list.
TLocalObjectMap m_mapLocalObjects; ///< Map with local objects indexed by object pointer.
TIsolationMap m_mapIsolatedObjects; ///< Map with isolated objects.
TObjectMap m_mapObjects; ///< Map with all objects indexed by the object ID.
TConfigSet m_setConfigObjects; ///< Set with the objects for storing in the configuration.
sdv::TInterfaceAccessPtr m_ptrCoreRepoAccess; ///< Linked core repository access (proxy interface).
bool m_bIsoObjectLoaded = false; ///< When set, the isolated object has loaded. Do not allow
///< another object of type complex service or utility to be
///< created.
};
#ifndef DO_NOT_INCLUDE_IN_UNIT_TEST
/**
* @brief Repository service
*/
class CRepositoryService : public sdv::CSdvObject
{
public:
CRepositoryService() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_CHAIN_MEMBER(GetRepository())
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IRepositoryInfo, GetRepository())
SDV_INTERFACE_SET_SECTION_CONDITION(EnableRepositoryObjectControl(), 1)
SDV_INTERFACE_SECTION(1)
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IRepositoryControl, GetRepository())
SDV_INTERFACE_DEFAULT_SECTION()
SDV_INTERFACE_SET_SECTION_CONDITION(EnableRepositoryRegisterForeignApp(), 2)
SDV_INTERFACE_SECTION(2)
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::IRegisterForeignObject, GetRepository())
SDV_INTERFACE_DEFAULT_SECTION()
SDV_INTERFACE_SET_SECTION_CONDITION(EnableRepositoryLink(), 3)
SDV_INTERFACE_SECTION(3)
SDV_INTERFACE_ENTRY_MEMBER(sdv::core::ILinkCoreRepository, GetRepository())
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_NAME("RepositoryService")
DECLARE_OBJECT_SINGLETON()
/**
* @brief Get access to the repository.
* @return Returns a reference to the one repository of this module.
*/
static CRepository& GetRepository();
/**
* @brief When set, the repository object control access will be enabled.
* @return Returns whether object control interface access is granted.
*/
static bool EnableRepositoryObjectControl();
/**
* @brief When set, the foreign app registration into the repository will be enabled.
* @return Returns whether foreign app rgeistration interface access is granted.
*/
static bool EnableRepositoryRegisterForeignApp();
/**
* @brief When set, the core repository link access will be enabled.
* @return Returns whether core repository link interface access is granted.
*/
static bool EnableRepositoryLink();
};
DEFINE_SDV_OBJECT_NO_EXPORT(CRepositoryService)
#endif
#endif // !define VAPI_REPOSITORY_H

View File

@@ -0,0 +1,140 @@
#include "sdv_core.h"
#include "toml_parser_util.h" // Insert to create the factory of this utility
#include <fstream>
#include <sstream>
extern sdv::IInterfaceAccess* SDVCore()
{
static CSDVCore& rcore = CSDVCore::GetInstance();
return &rcore;
}
CSDVCore::CSDVCore()
{}
CSDVCore::~CSDVCore()
{}
CSDVCore& CSDVCore::GetInstance()
{
static CSDVCore core;
return core;
}
CAppControl& CSDVCore::GetAppControl()
{
return m_appctrl;
}
CModuleControl& CSDVCore::GetModuleControl()
{
return m_modulectrl;
}
CMemoryManager& CSDVCore::GetMemoryManager()
{
return m_memmgr;
}
CRepository& CSDVCore::GetRepository()
{
return m_repository;
}
CLoggerControl& CSDVCore::GetLoggerControl()
{
return m_loggerctrl;
}
CLogger& CSDVCore::GetDefaultLogger()
{
return m_defaultlogger;
}
CAppConfig& CSDVCore::GetAppConfig()
{
return m_appconfig;
}
CAppControl& GetAppControl()
{
return CSDVCore::GetInstance().GetAppControl();
}
CModuleControl& GetModuleControl()
{
return CSDVCore::GetInstance().GetModuleControl();
}
CMemoryManager& GetMemoryManager()
{
return CSDVCore::GetInstance().GetMemoryManager();
}
CRepository& GetRepository()
{
return CSDVCore::GetInstance().GetRepository();
}
CLoggerControl& GetLoggerControl()
{
return CSDVCore::GetInstance().GetLoggerControl();
}
CLogger& GetDefaultLogger()
{
return CSDVCore::GetInstance().GetDefaultLogger();
}
CAppConfig& GetAppConfig()
{
return CSDVCore::GetInstance().GetAppConfig();
}
std::filesystem::path GetCoreDirectory()
{
static std::filesystem::path pathCoreDir;
if (!pathCoreDir.empty()) return pathCoreDir;
#ifdef _WIN32
// Windows specific
std::wstring ssPath(32768, '\0');
MEMORY_BASIC_INFORMATION sMemInfo{};
if (!VirtualQuery(&pathCoreDir, &sMemInfo, sizeof(sMemInfo))) return pathCoreDir;
DWORD dwLength = GetModuleFileNameW(reinterpret_cast<HINSTANCE>(sMemInfo.AllocationBase), ssPath.data(), 32767);
ssPath.resize(dwLength);
pathCoreDir = std::filesystem::path(ssPath);
return pathCoreDir.remove_filename();
#elif __linux__
// Read the maps file. It contains all loaded SOs.
std::ifstream fstream("/proc/self/maps");
std::stringstream sstreamMap;
sstreamMap << fstream.rdbuf();
std::string ssMap = sstreamMap.str();
if (ssMap.empty())
return pathCoreDir; // Some error
// Find the "core_services.sdv"
size_t nPos = ssMap.find("core_services.sdv");
if (nPos == std::string::npos) return pathCoreDir;
size_t nEnd = nPos;
// Find the start... runbackwards until the beginning of the line and remember the earliest occurance of a slash
size_t nBegin = 0;
while (nPos && ssMap[nPos] != '\n')
{
if (ssMap[nPos] == '/')
nBegin = nPos;
nPos--;
}
if (!nBegin) nBegin = nPos;
// Return the path
pathCoreDir = ssMap.substr(nBegin, nEnd - nBegin);
return pathCoreDir;
#else
#error The OS is not supported!
#endif
}

View File

@@ -0,0 +1,150 @@
#ifndef CORE_H
#define CORE_H
#include <support/component_impl.h>
#include "app_control.h"
#include "module_control.h"
#include "memory.h"
#include "repository.h"
#include "logger_control.h"
#include "logger.h"
#include "app_config.h"
/**
* @brief SDV core instance class containing containing the instances for the core services.
*/
class CSDVCore : public sdv::IInterfaceAccess
{
public:
/**
* @brief Constructor
*/
CSDVCore();
/**
* \brief Destructor
*/
~CSDVCore();
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_CHAIN_MEMBER(m_appctrl)
SDV_INTERFACE_CHAIN_MEMBER(m_modulectrl)
SDV_INTERFACE_CHAIN_MEMBER(m_memmgr)
SDV_INTERFACE_CHAIN_MEMBER(m_repository)
SDV_INTERFACE_CHAIN_MEMBER(m_loggerctrl)
END_SDV_INTERFACE_MAP()
/**
* @brief The one and only instance.
* @return Reference to this class.
*/
static CSDVCore& GetInstance();
/**
* @brief Return the application control.
* @return Reference to the application control.
*/
CAppControl& GetAppControl();
/**
* @brief Return the module control.
* @return Reference to the module control.
*/
CModuleControl& GetModuleControl();
/**
* @brief Return the memory manager.
* @return Reference to the memory manager.
*/
CMemoryManager& GetMemoryManager();
/**
* @brief Return the repository.
* @return Reference to the repository.
*/
CRepository& GetRepository();
/**
* @brief Return the logger control.
* @return Reference to the logger control.
*/
CLoggerControl& GetLoggerControl();
/**
* @brief Return the default logger.
* @return Reference to the default logger.
*/
CLogger& GetDefaultLogger();
/**
* @brief Return the application config class.
* @return Reference to the application config class.
*/
CAppConfig& GetAppConfig();
private:
CMemoryManager m_memmgr; ///< Memory manager - note: needs to be first in the list of members!
CAppControl m_appctrl; ///< Application control
CRepository m_repository; ///< Repository - note: repository should be present before module control!
CModuleControl m_modulectrl; ///< Module control
CLogger m_defaultlogger; ///< Default logger - note: the logger must be present before the logger control!
CLoggerControl m_loggerctrl; ///< Logger control
CAppConfig m_appconfig; ///< Application configuration class
};
/**
* @brief Exported function for core access.
* @return Pointer to the interface of the core library.
*/
extern "C" SDV_SYMBOL_PUBLIC sdv::IInterfaceAccess* SDVCore();
/**
* @brief Return the application control.
* @return Reference to the application control.
*/
CAppControl& GetAppControl();
/**
* @brief Return the module control.
* @return Reference to the module control.
*/
CModuleControl& GetModuleControl();
/**
* @brief Return the memory manager.
* @return Reference to the memory manager.
*/
CMemoryManager& GetMemoryManager();
/**
* @brief Return the repository.
* @return Reference to the repository.
*/
CRepository& GetRepository();
/**
* @brief Return the logger control.
* @return Reference to the logger control.
*/
CLoggerControl& GetLoggerControl();
/**
* @brief Return the default logger.
* @attention Use the logger control to access the logger.
* @return Reference to the default logger.
*/
CLogger& GetDefaultLogger();
/**
* @brief Return the application config class.
* @return Reference to the application config class.
*/
CAppConfig& GetAppConfig();
/**
* @brief Get the location of the core_services.sdv.
* @return Path to the directory containing the loaded core directory.
*/
std::filesystem::path GetCoreDirectory();
#endif // !defined CORE_H

View File

@@ -0,0 +1,323 @@
#include "character_reader_utf_8.h"
#include "exception.h"
CCharacterReaderUTF8::CCharacterReaderUTF8() : m_nDataLength{0}, m_nCursor{0}
{}
CCharacterReaderUTF8::CCharacterReaderUTF8(const std::string& rssString) :
m_ssString{rssString}, m_nDataLength{rssString.size()}, m_nCursor{0}
{
CheckForInvalidUTF8Bytes();
CheckForInvalidUTF8Sequences();
}
void CCharacterReaderUTF8::Feed(const std::string& rssString)
{
m_ssString = rssString;
m_nDataLength = rssString.size();
m_nCursor = 0;
CheckForInvalidUTF8Bytes();
CheckForInvalidUTF8Sequences();
}
void CCharacterReaderUTF8::Reset()
{
m_ssString.clear();
m_nDataLength = 0;
m_nCursor = 0;
}
std::string CCharacterReaderUTF8::Peek()
{
return GetNextCharacter();
}
std::string CCharacterReaderUTF8::Peek(std::size_t n)
{
if (n < 1)
{
return "";
}
size_t offset{0};
for (std::size_t i = 0; i < n - 1; ++i)
{
offset += GetLengthOfNextCharacter(offset);
if (m_nDataLength <= m_nCursor + offset)
{
return "";
}
}
return GetNextCharacter(offset);
}
std::string CCharacterReaderUTF8::PeekUntil(const std::vector<std::string>& lstCollection)
{
size_t offset{0};
bool found{false};
std::string accumulation;
while (!found && m_nDataLength > m_nCursor + offset)
{
std::string character = GetNextCharacter(offset);
offset += GetLengthOfNextCharacter(offset);
for (const auto& delimiter : lstCollection)
{
if (delimiter == character)
{
found = true;
}
}
if (!found)
{
accumulation += character;
}
}
return accumulation;
}
std::string CCharacterReaderUTF8::Consume()
{
if (IsEOF())
{
return "";
}
std::string character{GetNextCharacter()};
m_nCursor += GetLengthOfNextCharacter();
return character;
}
std::string CCharacterReaderUTF8::Consume(std::size_t n)
{
if (n < 1)
{
return "";
}
size_t offset{0};
for (uint32_t i = 0; i < n - 1; ++i)
{
offset += GetLengthOfNextCharacter(offset);
if (m_nDataLength < m_nCursor + offset)
{
return "";
}
}
std::string character{GetNextCharacter(offset)};
m_nCursor += offset + GetLengthOfNextCharacter(offset);
return character;
}
std::string CCharacterReaderUTF8::ConsumeUntil(const std::vector<std::string>& lstCollection)
{
std::size_t offset{0};
bool found{false};
std::string accumulation;
while (!found && m_nDataLength > m_nCursor + offset)
{
std::string character = GetNextCharacter(offset);
offset += GetLengthOfNextCharacter(offset);
for (const auto& delimiter : lstCollection)
{
if (delimiter == character)
{
found = true;
}
}
if (!found)
{
accumulation += character;
}
else
{
offset -= character.size();
}
}
m_nCursor += offset;
return accumulation;
}
bool CCharacterReaderUTF8::IsEOF() const
{
return m_nDataLength < m_nCursor + 1;
}
void CCharacterReaderUTF8::CheckForInvalidUTF8Bytes() const
{
const unsigned char invalidByteC0{0xC0};
const unsigned char invalidByteC1{0xC1};
const unsigned char lowerBoundInvalidRegion{0xF5};
for (std::size_t i = 0; i < m_ssString.size(); ++i)
{
unsigned char uc = m_ssString[i];
if (uc == invalidByteC0 || uc == invalidByteC1 || uc >= lowerBoundInvalidRegion)
{
std::stringstream message;
message << "Invalid byte " << std::hex << uc << std::dec << " at position " << i << "\n";
throw XTOMLParseException(message.str());
}
}
}
void CCharacterReaderUTF8::CheckForInvalidUTF8Sequences() const
{
enum class EState
{
state_neutral,
state_two_byte,
state_three_byte,
state_four_byte,
state_error
};
EState eCurrentState{EState::state_neutral};
uint8_t uiIndex{0};
auto fnCheckByteInNeutralState = [&uiIndex, &eCurrentState](unsigned char uc)
{
if ((uc & m_uiOneByteCheckMask) == m_OneByteCheckValue)
{
eCurrentState = EState::state_neutral;
uiIndex = 0;
}
else if ((uc & m_uiFourByteCheckMask) == m_uiFourByteCheckValue)
{
eCurrentState = EState::state_four_byte;
uiIndex = 1;
}
else if ((uc & m_uiThreeByteCheckMask) == m_uiThreeByteCheckValue)
{
eCurrentState = EState::state_three_byte;
uiIndex = 1;
}
else if ((uc & m_uiTwoByteCheckMask) == m_uiTwoByteCheckValue)
{
eCurrentState = EState::state_two_byte;
uiIndex = 1;
}
else
{
eCurrentState = EState::state_error;
}
};
auto fnCheckByteInTwoByteState = [&uiIndex, &eCurrentState](unsigned char uc)
{
if ((uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
{
uiIndex = 0;
eCurrentState = EState::state_neutral;
}
else
{
eCurrentState = EState::state_error;
}
};
auto fnCheckByteInThreeByteState = [&uiIndex, &eCurrentState](unsigned char uc)
{
if (uiIndex == 1 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
{
uiIndex = 2;
}
else if (uiIndex == 2 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
{
uiIndex = 0;
eCurrentState = EState::state_neutral;
}
else
{
eCurrentState = EState::state_error;
}
};
auto fnCheckByteInFourByteState = [&uiIndex, &eCurrentState](unsigned char uc)
{
if (uiIndex <= 2 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
{
++uiIndex;
}
else if (uiIndex == 3 && (uc & m_uiFollowByteCheckMask) == m_uiFollowByteValue)
{
uiIndex = 0;
eCurrentState = EState::state_neutral;
}
else
{
eCurrentState = EState::state_error;
}
};
for (std::size_t i = 0; i < m_ssString.size(); ++i)
{
uint8_t uiCurrentByte = m_ssString[i];
switch (eCurrentState)
{
case EState::state_neutral:
fnCheckByteInNeutralState(uiCurrentByte);
break;
case EState::state_two_byte:
fnCheckByteInTwoByteState(uiCurrentByte);
break;
case EState::state_three_byte:
fnCheckByteInThreeByteState(uiCurrentByte);
break;
case EState::state_four_byte:
fnCheckByteInFourByteState(uiCurrentByte);
break;
default:
std::stringstream sstreamMessage;
sstreamMessage << "Invalid character with byte " << std::hex << m_ssString[i - 1] << std::dec << "("
<< static_cast<int32_t>(m_ssString[i - 1]) << ") at index " << i - 1 << "\n";
throw XTOMLParseException(sstreamMessage.str());
break;
}
}
if (eCurrentState != EState::state_neutral)
{
std::stringstream sstreamMessage;
sstreamMessage << "Unfinished character at the end of file\n";
throw XTOMLParseException(sstreamMessage.str());
}
}
std::string CCharacterReaderUTF8::GetNextCharacter()
{
if (IsEOF())
{
return "";
}
return {m_ssString, m_nCursor, GetLengthOfNextCharacter()};
}
std::string CCharacterReaderUTF8::GetNextCharacter(std::size_t offset)
{
return {m_ssString, m_nCursor + offset, GetLengthOfNextCharacter(offset)};
}
size_t CCharacterReaderUTF8::GetLengthOfNextCharacter() const
{
return GetLengthOfNextCharacter(0);
}
size_t CCharacterReaderUTF8::GetLengthOfNextCharacter(std::size_t offset) const
{
uint8_t ui = m_ssString[m_nCursor + offset];
int32_t ret;
if ((ui & m_uiOneByteCheckMask) == m_OneByteCheckValue)
{
ret = 1;
}
else if ((ui & m_uiFourByteCheckMask) == m_uiFourByteCheckValue)
{
ret = 4;
}
else if ((ui & m_uiThreeByteCheckMask) == m_uiThreeByteCheckValue)
{
ret = 3;
}
else if ((ui & m_uiTwoByteCheckMask) == m_uiTwoByteCheckValue)
{
ret = 2;
}
else
{
std::stringstream sstreamMessage;
sstreamMessage << "Invalid character sequence with byte " << std::hex << ui << std::dec
<< " as start byte\n";
throw XTOMLParseException(sstreamMessage.str());
}
return ret;
}

View File

@@ -0,0 +1,127 @@
#ifndef CHARACTER_READER_UTF_8_H
#define CHARACTER_READER_UTF_8_H
#include <sstream>
#include <stdexcept>
#include <vector>
#include <cstdint>
/**
* @brief Reads a given input string, interprets bytes as UTF-8 and returns UTF-8 characters or strings in order on
* demand
*/
class CCharacterReaderUTF8
{
public:
/**
* @brief Standard constructor for empty character reader
*/
CCharacterReaderUTF8();
/**
* @brief Constructs a character reader from a given string
* @param[in] rssString UTF-8 input string.
* @throw InvalidCharacterException Throws an InvalidCharacterException if the input contains invalid UTF-8 characters
* @throw InvalidByteException Throws an InvalidByteException if the input contains for UTF-8 invalid bytes
*/
CCharacterReaderUTF8(const std::string& rssString);
/**
* @brief Feed the character reader from the given string.
* @param[in] rssString UTF-8 input string.
*/
void Feed(const std::string& rssString);
/**
* @brief Reset the character reader content.
*/
void Reset();
/**
* @brief Gets the next UTF-8 character without advancing the cursor
* @return Returns the next UTF-8 character after the current cursor or an empty string if the cursor is at the
* end
*/
std::string Peek();
/**
* @brief Gets the next n-th UTF-8 character without advancing the cursor
* @param[in] n Step size
* @return Returns the n-th UTF-8 character after the current cursor or an empty string if n<1 or it would read
* after the last character
*/
std::string Peek(std::size_t n);
/**
* @brief Gets all upcoming UTF-8 characters until a terminating character without advancing the cursor
* @param[in] lstCollection A collection of terminating characters
* @return Returns a string of UTF-8 characters until (excluding) the first occurrence of one of the given
* characters
*/
std::string PeekUntil(const std::vector<std::string>& lstCollection);
/**
* @brief Gets the next UTF-8 character and advancing the cursor by one
* @return Returns the next UTF-8 character after the current cursor or an empty string if the cursor is at the
* end
*/
std::string Consume();
/**
* @brief Gets the next n-th UTF-8 character and advancing the cursor by n
* @param[in] n Step size
* @return Returns the n-th UTF-8 character after the current cursor or an empty string if n<1 or it would read
* after the last character
*/
std::string Consume(std::size_t n);
/**
* @brief Gets all upcoming UTF-8 characters until a terminating character and advancing the cursor by the number
* of characters in the returned string
* @param[in] lstCollection A collection of terminating characters
* @return Returns a string of UTF-8 characters until excludingg) the first occurrence of one of the given
* characters
*/
std::string ConsumeUntil(const std::vector<std::string>& lstCollection);
/**
* @brief Checks if the cursor is at the end of the data to read
* @return Returns true if the cursor is at the end of the readable data, false otherwise
*/
bool IsEOF() const;
private:
void CheckForInvalidUTF8Bytes() const;
void CheckForInvalidUTF8Sequences() const;
std::string GetNextCharacter();
std::string GetNextCharacter(std::size_t offset);
size_t GetLengthOfNextCharacter() const;
size_t GetLengthOfNextCharacter(std::size_t offset) const;
static const uint8_t m_uiOneByteCheckMask{0b10000000}; //!< Checkmask for 1-Byte UTF-8 characters
static const uint8_t m_OneByteCheckValue{0b00000000}; //!< Value of a 1-Byte UTF-8 character
//!< after being or-ed with the checkmask
static const uint8_t m_uiFollowByteCheckMask{0b11000000}; //!< Checkmask for followbyte of a multi-Byte UTF-8 character
static const uint8_t m_uiFollowByteValue{0b10000000}; //!< Value of a followbyte of a multi-Byte UTF-8 character
//!< after being or-ed with the checkmask
static const uint8_t m_uiTwoByteCheckMask{0b11100000}; //!< Checkmask for startbyte of 2-Byte UTF-8 characters
static const uint8_t m_uiTwoByteCheckValue{0b11000000}; //!< Value of a startbyte of a 2-Byte UTF-8 character
//!< after being or-ed with the checkmask
static const uint8_t m_uiThreeByteCheckMask{0b11110000}; //!< Checkmask for startbyte of 3-Byte UTF-8 characters
static const uint8_t m_uiThreeByteCheckValue{0b11100000}; //!< Value of a startbyte of a 3-Byte UTF-8 character
//!< after being or-ed with the checkmask
static const uint8_t m_uiFourByteCheckMask{0b11111000}; //!< Checkmask for startbyte of 4-Byte UTF-8 characters
static const uint8_t m_uiFourByteCheckValue{0b11110000}; //!< Value of a startbyte of a 4-Byte UTF-8 character
//!< after being or-ed with the checkmask
std::string m_ssString;
std::size_t m_nDataLength;
std::size_t m_nCursor;
};
#endif // CHARACTER_READER_UTF_8_H

View File

@@ -0,0 +1,20 @@
#ifndef CONFIG_EXCEPTION_H
#define CONFIG_EXCEPTION_H
#include <interfaces/toml.h>
except XTOMLParseException : public sdv::toml::XTOMLParseException
{
/**
* @brief Constructor
*/
XTOMLParseException(const std::string& rss) { ssMessage = rss; };
/**
* @brief Return the explanatory string.
* @return The descriptive string.
*/
virtual const char* what() const noexcept override { return ssMessage.c_str(); }
};
#endif // !defined CONFIG_EXCEPTION_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,226 @@
#ifndef LEXER_TOML_H
#define LEXER_TOML_H
#include <algorithm>
#include <stack>
#include "character_reader_utf_8.h"
/**
* @brief Tokenizes the output of a character reader in regard of the TOML format and returns tokens in order on demand
*/
class CLexerTOML
{
public:
/**
* @brief Enum for all possible token categories
*/
enum class ETokenCategory : uint8_t
{
token_none, ///< Default
token_syntax_assignment, ///< '='
token_syntax_array_open, ///< '[' after '='
token_syntax_array_close, ///< ']' after an array open
token_syntax_table_open, ///< '['
token_syntax_table_close, ///< ']'
token_syntax_table_array_open, ///< '[['
token_syntax_table_array_close, ///< ']]'
token_syntax_inline_table_open, ///< '{'
token_syntax_inline_table_close, ///< '}'
token_syntax_comma, ///< ','
token_syntax_dot, ///< '.'
token_syntax_new_line, ///< Line break
token_key, ///< Key of a Key-Value-Pair
token_string, ///< A string for a Value of a Key-Value-Pair or Array
token_integer, ///< An integer for a Value of a Key-Value-Pair or Array
token_float, ///< A floating point number for a Value of a Key-Value-Pair or Array
token_boolean, ///< A bool for a Value of a Key-Value-Pair or Array
token_time_local, ///< Unused for now
token_date_time_offset, ///< Unused for now
token_date_time_local, ///< Unused for now
token_date_local, ///< Unused for now
token_eof, ///< End of File Token; may only be at the end of the token array
token_error, ///< Error token containing an error message; further lexing is not affected
token_empty, ///< Empty token for trying to read out of bounds
token_terminated, ///< Terminated token containing an error message; further lexing is terminated
};
/**
* @brief Contains lexed information for the parser
*/
struct SToken
{
/**
* @brief Default constructor
*/
SToken() = default;
/**
* @brief Constructs a new Token object with a given category
* @param[in] category The initial token category value for the token to be constructed
*/
explicit SToken(ETokenCategory category) : eCategory(category)
{}
std::string ssContentString; ///< Token string content
int64_t iContentInteger{}; ///< Token integer content
double dContentFloatingpoint{}; ///< Token floatingpoint content
bool bContentBoolean{}; ///< Token boolean content
ETokenCategory eCategory{ETokenCategory::token_none}; ///< Token category
};
/**
* @brief Default constructor
*/
CLexerTOML() = default;
/**
* @brief Constructs a new LexerTOML object with given input data that will be lexed
* @param[in] rssString The UTF-8 encoded content of a TOML source
*/
CLexerTOML(const std::string& rssString);
/**
* @brief Feed the lexer with the given string.
* @param[in] rssString UTF-8 input string.
*/
void Feed(const std::string& rssString);
/**
* @brief Reset the lexer content.
*/
void Reset();
/**
* @brief Gets the next token after the current cursor position without advancing the cursor
* @return Returns the next token after the current cursor position or a End-of-File-Token if there are no
* tokens
*/
SToken Peek() const;
/**
* @brief Gets the next token after the current cursor position and advancing the cursor by one
* @return Returns the next token after the current cursor position or a End-of-File-Token if there are no
* tokens
*/
SToken Consume();
/**
* @brief Gets the n-th token after the current cursor without advancing the cursor
* @param[in] n Step size
* @return Returns the n-th token after the current cursor position or and empty token if n<1 or the end-token
* if a step of n would read a position after the end-token
*/
SToken Peek(int32_t n);
/**
* @brief Gets the n-th token after the current cursor and advancing the cursor by n
* @param[in] n Step size
* @return Returns the n-th token after the current cursor position or and empty token if n<1 or the end-token
* if a step of n would read a position after the end-token
*/
SToken Consume(int32_t n);
/**
* @brief Checks if the end-token was consumed
* @return Returns true if the end-token was consumed by Consume() or Consume(n) or if there are no tokens;
* false otherwise
*/
bool IsEnd() const;
private:
void GenerateTokens();
bool IsBasicQuotedKey();
void ReadBasicQuotedKey();
bool IsLiteralQuotedKey();
void ReadLiteralQuotedKey();
bool IsBareKey();
void ReadBareKey();
bool IsBasicString();
void ReadBasicString();
bool IsBasicMultilineString();
void ReadBasicMultilineString();
bool IsLiteralString();
void ReadLiteralString();
bool IsLiteralMultilineString();
void ReadLiteralMultilineString();
bool IsInteger();
void ReadInteger();
bool IsFloat();
void ReadFloat();
bool IsBool();
void ReadBool();
bool IsWhitespace();
void ReadWhitespace();
bool IsSyntaxElement();
void ReadSyntaxElement();
bool IsComment();
void ReadComment();
void ReadUnknownSequence();
std::string Unescape();
std::string Unicode4DigitToUTF8();
std::string Unicode8DigitToUTF8();
std::string UnicodeToUTF8(uint8_t numCharacters);
static uint32_t HexToDecimal(const char character);
static uint32_t DecimalToDecimal(const char character);
static uint32_t OctalToDecimal(const char character);
static uint32_t BinaryToDecimal(const char character);
CCharacterReaderUTF8 m_reader;
std::vector<SToken> m_vecTokens;
std::size_t m_nCursor{0};
/**
* @brief Enum for differentiating between keys and values that are potentially indifferent like '"value" =
* "value"'
*/
enum class EExpectation
{
expect_key, ///< A key is expected over a value
expect_value, ///< A value is expected over a key
expect_value_once, ///< A value is expected over a key once
};
std::stack<EExpectation> m_stackExpectations; ///< Tracking of key or value expectations in nested structures
// int32_t m_LineCount{0};
const std::vector<std::string> m_vecKeyDelimiters{
"\n", "\t", "\r", " ", "", ".", "=", "]"}; ///< Characters that delimit a key
const std::vector<std::string> m_vecValueDelimiters{
"\n", "\t", "\r", " ", ",", "", "]", "}"}; ///< Characters that delimit a value
};
#endif // LEXER_TOML_H

View File

@@ -0,0 +1,760 @@
#include <algorithm>
#include "parser_node_toml.h"
#include "exception.h"
#include <sstream>
size_t FindFirst(const std::string& rss, const std::string& rssSeparator /*= "."*/)
{
enum class EType {normal, single_quoted_string, double_quoted_string} eType = EType::normal;
size_t nPos = 0;
while (nPos < rss.size())
{
switch (rss[nPos])
{
case '\'':
if (eType == EType::normal)
eType = EType::single_quoted_string;
else if (eType == EType::single_quoted_string)
eType = EType::normal;
break;
case '\"':
if (eType == EType::normal)
eType = EType::double_quoted_string;
else if (eType == EType::double_quoted_string)
eType = EType::normal;
break;
case '\\':
nPos++;
break;
default:
if (eType == EType::normal && rssSeparator.find(rss[nPos]) != std::string::npos)
return nPos;
break;
}
nPos++;
}
return nPos >= rss.size() ? std::string::npos : nPos;
}
size_t FindLast(const std::string& rss, const std::string& rssSeparator /*= "."*/)
{
enum class EType {normal, single_quoted_string, double_quoted_string} eType = EType::normal;
size_t nPos = rss.size();
while (nPos)
{
nPos--;
bool bEscaped = nPos && rss[nPos - 1] == '\\';
switch (rss[nPos])
{
case '\'':
if (bEscaped)
nPos--;
else if (eType == EType::normal)
eType = EType::single_quoted_string;
else if (eType == EType::single_quoted_string)
eType = EType::normal;
break;
case '\"':
if (bEscaped)
nPos--;
else if (eType == EType::normal)
eType = EType::double_quoted_string;
else if (eType == EType::double_quoted_string)
eType = EType::normal;
break;
default:
if (eType == EType::normal && rssSeparator.find(rss[nPos]) != std::string::npos)
return nPos;
break;
}
}
return std::string::npos;
}
bool CompareEqual(const std::string& rss1, const std::string& rss2)
{
size_t nStart1 = 0, nStop1 = rss1.size();
if (rss1.size() && rss1.find_first_of("\"\'") == 0 && rss1.find_last_of("\"\'") == (rss1.size() - 1))
{
nStart1++;
nStop1--;
}
size_t nStart2 = 0, nStop2 = rss2.size();
if (rss2.size() && rss2.find_first_of("\"\'") == 0 && rss2.find_last_of("\"\'") == (rss2.size() - 1))
{
nStart2++;
nStop2--;
}
if (nStop1 - nStart1 != nStop2 - nStart2) return false;
for (size_t n = 0; n < (nStop1 - nStart1); n++)
{
if (rss1[nStart1 + n] != rss2[nStart2 + n])
return false;
}
return true;
}
std::string EscapeString(const std::string& rssString, const char cQuoteType /*= '\"'*/)
{
// Iterate through the string
std::stringstream sstream;
size_t nPos = 0;
uint32_t uiUTFChar = 0;
while (nPos < rssString.size())
{
uint8_t uiChar = static_cast<uint8_t>(rssString[nPos]);
switch (uiChar)
{
case '\a': sstream << "\\a"; break;
case '\b': sstream << "\\b"; break;
case '\f': sstream << "\\f"; break;
case '\n': sstream << "\\n"; break;
case '\r': sstream << "\\r"; break;
case '\t': sstream << "\\t"; break;
case '\v': sstream << "\\v"; break;
case '\\': sstream << "\\\\"; break;
case '\'': if (static_cast<uint8_t>(cQuoteType) == uiChar) sstream << "\\"; sstream << "\'"; break;
case '\"': if (static_cast<uint8_t>(cQuoteType) == uiChar) sstream << "\\"; sstream << "\""; break;
default:
if (uiChar >= 0x20 && uiChar < 0x7f)
{
// Standard ASCII
sstream << static_cast<char>(uiChar);
break;
}
else if (uiChar <= 0x80) // One byte UTF-8
uiUTFChar = static_cast<uint32_t>(uiChar);
else if (uiChar <= 0xDF) // Two bytes UTF-8
{
uiUTFChar = static_cast<size_t>(uiChar & 0b00011111) << 6;
// Expecting the next character to be between 0x80 and 0xBF
nPos++;
if (nPos >= rssString.size()) break;
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
}
else if (uiChar <= 0xEF) // Three bytes UTF-8
{
uiUTFChar = static_cast<size_t>(uiChar & 0b00001111) << 6;
// Expecting the next character to be between 0x80 and 0xBF
nPos++;
if (nPos >= rssString.size()) break;
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
uiUTFChar <<= 6;
// Expecting the next character to be between 0x80 and 0xBF
nPos++;
if (nPos >= rssString.size()) break;
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
}
else if (uiChar <= 0xF7) // Four bytes UTF-8
{
uiUTFChar = static_cast<size_t>(uiChar & 0b00000111) << 6;
// Expecting the next character to be between 0x80 and 0xBF
nPos++;
if (nPos >= rssString.size()) break;
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
uiUTFChar <<= 6;
// Expecting the next character to be between 0x80 and 0xBF
nPos++;
if (nPos >= rssString.size()) break;
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
uiUTFChar <<= 6;
// Expecting the next character to be between 0x80 and 0xBF
nPos++;
if (nPos >= rssString.size()) break;
uiUTFChar |= static_cast<size_t>(rssString[nPos] & 0b00111111);
}
// Stream the UTF character
if (uiUTFChar <= 0xFFFF)
sstream << "\\u" << std::uppercase << std::hex << std::setfill('0') << std::setw(4) << uiUTFChar;
else
sstream << "\\U" << std::uppercase << std::hex << std::setfill('0') << std::setw(8) << uiUTFChar;
break;
}
nPos++;
}
return sstream.str();
}
CNode::CNode(const std::string& rssName) : m_ssName(rssName)
{}
CNode::~CNode()
{}
sdv::u8string CNode::GetName() const
{
return m_ssName;
}
sdv::any_t CNode::GetValue() const
{
return sdv::any_t();
}
sdv::u8string CNode::GetTOML() const
{
std::string ssLastTable;
std::string ssParent;
return CreateTOMLText(ssParent, ssLastTable, true, false, true, true);
}
std::shared_ptr<const CArray> CNode::GetArray() const
{
if (!dynamic_cast<const CArray*>(this)) return {};
return std::static_pointer_cast<const CArray>(shared_from_this());
}
std::shared_ptr<CArray> CNode::GetArray()
{
if (!dynamic_cast<CArray*>(this)) return {};
return std::static_pointer_cast<CArray>(shared_from_this());
}
std::shared_ptr<const CTable> CNode::GetTable() const
{
if (!dynamic_cast<const CTable*>(this)) return {};
return std::static_pointer_cast<const CTable>(shared_from_this());
}
std::shared_ptr<CTable> CNode::GetTable()
{
if (!dynamic_cast<const CTable*>(this)) return {};
return std::static_pointer_cast<CTable>(shared_from_this());
}
std::weak_ptr<const CNode> CNode::GetParent() const
{
return m_ptrParent;
}
void CNode::SetParent(const std::shared_ptr<CNode>& rptrParent)
{
m_ptrParent = rptrParent;
}
std::shared_ptr<CNode> CNode::Find(const std::string& /*rssPath*/) const
{
return std::shared_ptr<CNode>();
}
std::shared_ptr<CNode> CNode::GetDirect(const std::string& /*rssPath*/) const
{
// The CNode implementation doesn't have any children. Therefore there is nothing to get.
return std::shared_ptr<CNode>();
}
std::string CNode::CreateTOMLText(const std::string& rssParent /*= std::string()*/) const
{
std::string ssLastTable;
return CreateTOMLText(rssParent, ssLastTable);
}
void CNode::Add(const std::string& rssPath, const std::shared_ptr<CNode>& /*rptrNode*/, bool /*bDefinedExplicitly = true*/)
{
throw XTOMLParseException(("Not allowed to add '" + rssPath + "'; parent node is final").c_str());
}
CBooleanNode::CBooleanNode(const std::string& rssName, bool bVal) : CNode(rssName), m_bVal(bVal)
{}
sdv::toml::ENodeType CBooleanNode::GetType() const
{
return sdv::toml::ENodeType::node_boolean;
}
sdv::any_t CBooleanNode::GetValue() const
{
return sdv::any_t(m_bVal);
}
std::string CBooleanNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool /*bRoot*/) const
{
std::stringstream sstreamEntry;
// Do we need to start a table?
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
{
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
rssLastPrintedTable = rssParent;
}
if (bEmbedded && !bFirst) // 2nd or higher array entry
sstreamEntry << ", ";
if (!bEmbedded || bAssignment) // Not an array entry
sstreamEntry << GetName() << " = ";
sstreamEntry << (m_bVal ? "true" : "false");
if (!bEmbedded) // Not an array entry
sstreamEntry << std::endl;
return sstreamEntry.str();
}
CIntegerNode::CIntegerNode(const std::string& rssName, int64_t iVal) : CNode(rssName), m_iVal(iVal)
{}
sdv::toml::ENodeType CIntegerNode::GetType() const
{
return sdv::toml::ENodeType::node_integer;
}
sdv::any_t CIntegerNode::GetValue() const
{
return sdv::any_t(m_iVal);
}
std::string CIntegerNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool /*bRoot*/) const
{
std::stringstream sstreamEntry;
// Do we need to start a table?
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
{
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
rssLastPrintedTable = rssParent;
}
if (bEmbedded && !bFirst) // 2nd or higher array entry
sstreamEntry << ", ";
if (!bEmbedded || bAssignment) // Not an array entry
sstreamEntry << GetName() << " = ";
sstreamEntry << m_iVal;
if (!bEmbedded) // Not an array entry
sstreamEntry << std::endl;
return sstreamEntry.str();
}
CFloatingPointNode::CFloatingPointNode(const std::string& rssName, double dVal) : CNode(rssName), m_dVal(dVal)
{}
sdv::toml::ENodeType CFloatingPointNode::GetType() const
{
return sdv::toml::ENodeType::node_floating_point;
}
sdv::any_t CFloatingPointNode::GetValue() const
{
return sdv::any_t(m_dVal);
}
std::string CFloatingPointNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst,
bool bEmbedded, bool bAssignment, bool /*bRoot*/) const
{
std::stringstream sstreamEntry;
// Do we need to start a table?
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
{
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
rssLastPrintedTable = rssParent;
}
if (bEmbedded && !bFirst) // 2nd or higher array entry
sstreamEntry << ", ";
if (!bEmbedded || bAssignment) // Not an array entry
sstreamEntry << GetName() << " = ";
sstreamEntry << std::setprecision(15) << std::defaultfloat << m_dVal;
if (!bEmbedded) // Not an array entry
sstreamEntry << std::endl;
return sstreamEntry.str();
}
CStringNode::CStringNode(const std::string& rssName, const std::string& rssVal) : CNode(rssName), m_ssVal(rssVal)
{}
sdv::toml::ENodeType CStringNode::GetType() const
{
return sdv::toml::ENodeType::node_string;
}
sdv::any_t CStringNode::GetValue() const
{
return sdv::any_t(m_ssVal);
}
std::string CStringNode::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool /*bRoot*/) const
{
std::stringstream sstreamEntry;
// Do we need to start a table?
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
{
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
rssLastPrintedTable = rssParent;
}
if (bEmbedded && !bFirst) // 2nd or higher array entry
sstreamEntry << ", ";
if (!bEmbedded || bAssignment) // Not an array entry
sstreamEntry << GetName() << " = ";
sstreamEntry << "\"" << EscapeString(m_ssVal) << "\"";
if (!bEmbedded) // Not an array entry
sstreamEntry << std::endl;
return sstreamEntry.str();
}
CNodeCollection::CNodeCollection(const std::string& rssName) : CNode(rssName)
{}
uint32_t CNodeCollection::GetCount() const
{
return static_cast<uint32_t>(m_vecContent.size());
}
sdv::IInterfaceAccess* CNodeCollection::GetNode(/*in*/ uint32_t uiIndex) const
{
auto ptrNode = Get(uiIndex);
return static_cast<sdv::IInterfaceAccess*>(ptrNode.get());
}
std::shared_ptr<CNode> CNodeCollection::Get(uint32_t uiIndex) const
{
if (static_cast<size_t>(uiIndex) >= m_vecContent.size()) return nullptr;
return m_vecContent[uiIndex];
}
sdv::IInterfaceAccess* CNodeCollection::GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const
{
auto ptrNode = GetDirect(ssPath);
return static_cast<sdv::IInterfaceAccess*>(ptrNode.get());
}
bool CNodeCollection::AddElement(const std::shared_ptr<CNode>& rptrNode, bool bUnique /*= false*/)
{
if (!rptrNode) return false;
if (bUnique && std::find_if(m_vecContent.begin(), m_vecContent.end(), [&](const std::shared_ptr<CNode>& rptrNodeEntry)
{
return CompareEqual(rptrNodeEntry->GetName(), rptrNode->GetName());
}) != m_vecContent.end()) return false;
m_vecContent.push_back(rptrNode);
return true;
}
CTable::CTable(const std::string& rssName) : CNodeCollection(rssName)
{}
sdv::toml::ENodeType CTable::GetType() const
{
return sdv::toml::ENodeType::node_table;
}
std::shared_ptr<CNode> CTable::GetDirect(const std::string& rssPath) const
{
size_t nSeparator = FindFirst(rssPath, ".[");
std::string ssKey = rssPath.substr(0, nSeparator);
std::shared_ptr<CNode> ptrNode;
for (uint32_t uiIndex = 0; !ptrNode && uiIndex < GetCount(); uiIndex++)
{
std::shared_ptr<CNode> ptrNodeEntry = Get(uiIndex);
if (!ptrNodeEntry) continue;
if (CompareEqual(ptrNodeEntry->GetName(), ssKey)) ptrNode = ptrNodeEntry;
}
if (!ptrNode) return ptrNode; // Not found
// Done?
if (nSeparator == std::string::npos) return ptrNode;
// There is more...
if (rssPath[nSeparator] == '.') nSeparator++; // Skip dot
return ptrNode->GetDirect(rssPath.substr(nSeparator));
}
std::string CTable::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool bRoot) const
{
// Create the full name
std::string ssFullName = rssParent;
if (!GetName().empty() && !bRoot)
{
if (!ssFullName.empty()) ssFullName += ".";
ssFullName += GetName();
}
// Stream the table
std::stringstream sstreamEntry;
if (bEmbedded && !bFirst) // 2nd or higher array entry
sstreamEntry << ", ";
if (bEmbedded) // Embedded table in an array
sstreamEntry << "{";
for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++)
{
std::shared_ptr<CNode> ptrNode = Get(uiIndex);
if (!ptrNode) continue;
sstreamEntry << ptrNode->CreateTOMLText(ssFullName, rssLastPrintedTable, uiIndex == 0, bEmbedded);
}
if (bEmbedded) // Embedded table in an array
sstreamEntry << "}";
if (!bEmbedded && !bAssignment && !GetName().empty())
sstreamEntry << std::endl;
return sstreamEntry.str();
}
void CTable::Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly)
{
if (!m_bOpenToAddChildren)
throw XTOMLParseException(("Not allowed to add '" + rssPath + "'; parent node is final").c_str());
size_t nDotPos = FindFirst(rssPath);
std::string ssFirst = rssPath.substr(0, nDotPos);
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
auto ptrParent = Find(ssFirst);
// Element does not already exist at given path
if (!ptrParent)
{
// Add the new element as a direct child
if (nDotPos == std::string::npos)
{
rptrNode->SetParent(shared_from_this());
GetTable()->AddElement(rptrNode);
return;
}
// Add the new element as a descendant further down
auto ptrIntermediateTable = std::make_shared<CNormalTable>(ssFirst);
ptrIntermediateTable->SetParent(shared_from_this());
ptrIntermediateTable->m_bDefinedExplicitly = bDefinedExplicitly;
GetTable()->AddElement(ptrIntermediateTable);
static_cast<CNode*>(ptrIntermediateTable.get())->Add(ssSecond, rptrNode, bDefinedExplicitly);
return;
}
if (dynamic_cast<CTableArray*>(ptrParent.get()) && nDotPos == std::string::npos)
{
ptrParent->Add(ssFirst, rptrNode, bDefinedExplicitly);
return;
}
// Element already exists but would be inserted as a direct child
if (nDotPos == std::string::npos)
{
// Make an already implicitly defined table explicitly defined
if (ptrParent->GetType() == rptrNode->GetType() && dynamic_cast<CNodeCollection*>(ptrParent.get()) &&
!static_cast<CNodeCollection*>(ptrParent.get())->m_bDefinedExplicitly)
{
static_cast<CNodeCollection*>(ptrParent.get())->m_bDefinedExplicitly = true;
return;
}
throw XTOMLParseException(("Name '" + ssFirst + "' already exists").c_str());
}
// Element already exists and new element would be added as descendant
ptrParent->Add(ssSecond, rptrNode, bDefinedExplicitly);
}
std::shared_ptr<CNode> CTable::Find(const std::string& rssPath) const
{
size_t nDotPos = FindFirst(rssPath);
std::string ssFirst = rssPath.substr(0, nDotPos);
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++)
{
std::shared_ptr<CNode> ptrNode;
ptrNode = Get(uiIndex);
if (!ptrNode) continue;
if (CompareEqual(ptrNode->GetName(), ssFirst))
{
if (nDotPos == std::string::npos)
return ptrNode;
return ptrNode->Find(ssSecond);
}
}
// No node found...
return std::shared_ptr<CNode>();
}
CArray::CArray(const std::string& rssName) : CNodeCollection(rssName)
{}
sdv::toml::ENodeType CArray::GetType() const
{
return sdv::toml::ENodeType::node_array;
}
std::shared_ptr<CNode> CArray::GetDirect(const std::string& rssPath) const
{
size_t nIndexBegin = FindFirst(rssPath, "[");
if (nIndexBegin == std::string::npos) return std::shared_ptr<CNode>(); // Unexpected
size_t nIndexEnd = rssPath.find_first_not_of("0123456789", nIndexBegin + 1);
if (nIndexEnd == std::string::npos) return std::shared_ptr<CNode>(); // Unexpected
if (rssPath[nIndexEnd] != ']') return std::shared_ptr<CNode>(); // Unexpected
std::string ssIndex = rssPath.substr(nIndexBegin + 1, nIndexEnd - nIndexBegin - 1);
if (ssIndex.empty()) return std::shared_ptr<CNode>(); // Unexpected
uint32_t uiIndex = std::atol(ssIndex.c_str());
nIndexEnd++;
// Get the node
if (uiIndex >= GetCount()) return std::shared_ptr<CNode>(); // Not found
std::shared_ptr<CNode> ptrNode = Get(uiIndex);
// Done?
if (nIndexEnd == rssPath.size()) return ptrNode;
// Expecting a dot?
size_t nSeparator = nIndexEnd;
if (rssPath[nSeparator] == '.') nSeparator++; // Skip dot
return ptrNode->GetDirect(rssPath.substr(nSeparator));
}
std::string CArray::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool /*bRoot*/) const
{
std::stringstream sstreamEntry;
// Do we need to start a table?
if (!bEmbedded && bFirst && bAssignment && rssParent != rssLastPrintedTable)
{
sstreamEntry << std::endl << "[" << rssParent << "]" << std::endl;
rssLastPrintedTable = rssParent;
}
// Stream the array
if (bEmbedded && !bFirst) // 2nd or higher array entry
sstreamEntry << ", ";
if (!bEmbedded || bAssignment) // Not an array entry
sstreamEntry << GetName() << " = ";
sstreamEntry << "[";
for (uint32_t ui = 0; ui < GetCount(); ui++)
sstreamEntry << Get(ui)->CreateTOMLText(rssParent, rssLastPrintedTable, ui == 0, true, false);
sstreamEntry << "]";
if (!bEmbedded) // Not an array entry
sstreamEntry << std::endl;
return sstreamEntry.str();
}
void CArray::Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly)
{
size_t nDotPos = FindFirst(rssPath);
std::string ssFirst = rssPath.substr(0, nDotPos);
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
// Add new element to array
if (nDotPos == std::string::npos)
{
GetArray()->AddElement(rptrNode);
return;
}
// Add new element to subelement of array
if (std::any_of(ssFirst.begin(), ssFirst.end(), [](char digit) { return (digit < '0') || (digit > '9'); }))
{
throw XTOMLParseException(("Invalid array access subscript '" + ssFirst + "'").c_str());
}
uint32_t uiIndex = std::stoi(ssFirst);
if (uiIndex >= GetArray()->GetCount())
{
// This indicates an array within an arrays. Add the intermediate array
auto ptrIntermediateArray = std::make_shared<CNormalArray>(ssFirst);
ptrIntermediateArray->SetParent(shared_from_this());
ptrIntermediateArray->m_bDefinedExplicitly = bDefinedExplicitly;
GetArray()->AddElement(ptrIntermediateArray);
static_cast<CNode*>(ptrIntermediateArray.get())->Add(ssSecond, rptrNode, bDefinedExplicitly);
//throw XTOMLParseException(("Invalid array access index '" + ssFirst + "'; out of bounds").c_str());
}
GetArray()->Get(uiIndex)->Add(ssSecond, rptrNode, bDefinedExplicitly);
}
std::shared_ptr<CNode> CArray::Find(const std::string& rssPath) const
{
size_t nDotPos = FindFirst(rssPath);
std::string ssFirst = rssPath.substr(0, nDotPos);
std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
if (ssFirst.empty())
throw XTOMLParseException("Missing array subscript");
if (std::any_of(ssFirst.begin(), ssFirst.end(), [](char digit) { return (digit < '0') || (digit > '9'); }))
throw XTOMLParseException(("Invalid array access subscript '" + ssFirst + "'").c_str());
uint32_t uiIndex = std::stoi(ssFirst);
if (GetArray()->GetCount() <= uiIndex)
throw XTOMLParseException(
("Invalid array access index '" + ssFirst + "'; out of bounds").c_str());
if (nDotPos == std::string::npos)
return GetArray()->Get(uiIndex);
return GetArray()->Get(uiIndex)->Find(ssSecond);
}
std::string CTableArray::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool /*bFirst*/, bool /*bEmbedded*/,
bool /*bAssignment*/, bool bRoot) const
{
// Create the full name
std::string ssFullName = rssParent;
if (!ssFullName.empty() && !bRoot) ssFullName += ".";
ssFullName += GetName();
// Stream the array
std::stringstream sstreamEntry;
for (uint32_t ui = 0; ui < GetCount(); ui++)
{
sstreamEntry << std::endl << "[[" << ssFullName << "]]" << std::endl;
rssLastPrintedTable = ssFullName;
sstreamEntry << Get(ui)->CreateTOMLText(ssFullName, rssLastPrintedTable, ui == 0, false, false);
}
//sstreamEntry << std::endl;
return sstreamEntry.str();
}
void CTableArray::Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly)
{
//size_t nDotPos = FindFirst(rssPath);
//std::string ssFirst = rssPath.substr(0, nDotPos);
//std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
uint32_t uiSize = GetArray()->GetCount();
if (uiSize == 0)
{
throw XTOMLParseException("Trying to access table in an empty array of tables");
}
GetArray()->Get(uiSize - 1)->Add(rssPath, rptrNode, bDefinedExplicitly);
}
std::shared_ptr<CNode> CTableArray::Find(const std::string& rssPath) const
{
//size_t nDotPos = FindFirst(rssPath);
//std::string ssFirst = rssPath.substr(0, nDotPos);
//std::string ssSecond = nDotPos == std::string::npos ? "" : rssPath.substr(nDotPos + 1);
if (!GetArray()->GetCount())
throw XTOMLParseException(("Trying to access empty table array; " + rssPath).c_str());
return GetArray()->Get(GetArray()->GetCount() - 1)->Find(rssPath);
}
std::string CRootTable::CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool /*bFirst*/,
bool /*bEmbedded*/, bool /*bAssignment*/, bool /*bRoot*/) const
{
// Create the full name
std::string ssFullName = rssParent;
// Stream the table
std::stringstream sstreamEntry;
for (uint32_t uiIndex = 0; uiIndex < GetCount(); uiIndex++)
{
std::shared_ptr<CNode> ptrNode = Get(uiIndex);
if (!ptrNode) continue;
sstreamEntry << ptrNode->CreateTOMLText(ssFullName, rssLastPrintedTable);
}
sstreamEntry << std::endl;
// Skip whitespace at the beginning
std::string ssRet = sstreamEntry.str();
size_t nStart = ssRet.find_first_not_of(" \t\f\r\n\v");
if (nStart == std::string::npos) return std::string();
return ssRet.substr(nStart);
}

View File

@@ -0,0 +1,718 @@
#ifndef PARSER_NODE_TOML_H
#define PARSER_NODE_TOML_H
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
#include <interfaces/toml.h>
#include <support/interface_ptr.h>
// Forward declaration
class CArray;
class CTable;
/**
* @brief Find the first separator character. Do not include string content (single/or double quoted) and escape characters.
* @param[in] rss Reference to the string.
* @param[in] rssSeparator One of the characters to find in the string. Must not be an empty string!
* @return The position of the first separator character or std::string::npos when none has found.
*/
size_t FindFirst(const std::string& rss, const std::string& rssSeparator = ".");
/**
* @brief Find the last separator character. Do not include string content (single/or double quoted) and escape characters.
* @param[in] rss Reference to the string.
* @param[in] rssSeparator One of the characters to find in the string. Must not be an empty string!
* @return The position of the last separator character or std::string::npos when none has found.
*/
size_t FindLast(const std::string& rss, const std::string& rssSeparator = ".");
/**
* @brief Compare both string ignoring the quotes at the first position and last position.
* @param[in] rss1 Reference to the first string.
* @param[in] rss2 Reference to the second string.
* @return The comparison result.
*/
bool CompareEqual(const std::string& rss1, const std::string& rss2);
/**
* @brief Escape a string using escape characters and UTF values.
*/
std::string EscapeString(const std::string& rssString, const char cQuoteType = '\"');
/**
* @brief Node to build up the parse tree
*/
class CNode : public std::enable_shared_from_this<CNode>, public sdv::IInterfaceAccess, public sdv::toml::INodeInfo
{
protected:
/**
* @brief Constructs a new node object representing a table or an array.
* @param[in] rssName Reference to the name of the node.
*/
CNode(const std::string& rssName);
public:
/**
* @brief Deleted since Nodes should only be handled via smart-pointer
* @{
*/
CNode(const CNode&) = delete;
CNode& operator=(const CNode&) = delete;
CNode(const CNode&&) = delete;
CNode& operator=(const CNode&&) = delete;
/**
* @}
*/
/**
* @brief Destroy the node object
*/
~CNode();
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::toml::INodeInfo)
END_SDV_INTERFACE_MAP()
/**
* @brief Get the node name. Overload of sdv::toml::INodeInfo::GetName.
* @return String containing the name of the node.
*/
virtual sdv::u8string GetName() const override;
/**
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
* returns empty.
*/
virtual sdv::any_t GetValue() const override;
/**
* @brief The node value. Overload of sdv::toml::INodeInfo::GetTOML.
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
* returns empty.
*/
virtual sdv::u8string GetTOML() const override;
/**
* @brief Gets the array value of a node
* @return Returns a shared pointer of the array value stored in the node if the stored type is array
*/
std::shared_ptr<const CArray> GetArray() const;
/**
* @brief Gets the array value of a node
* @return Returns a shared pointer of the array value stored in the node if the stored type is array
*/
std::shared_ptr<CArray> GetArray();
/**
* @brief Gets the table value of a node
* @return Returns a shared pointer of the table value stored in the node if the stored type is table
*/
std::shared_ptr<const CTable> GetTable() const;
/**
* @brief Gets the table value of a node
* @return Returns a shared pointer of the table value stored in the node if the stored type is table
*/
std::shared_ptr<CTable> GetTable();
protected:
/**
* @brief Gets the Parent Node
* @return Returns the parent Node
* @attention Beware of expiring pointers
*/
std::weak_ptr<const CNode> GetParent() const;
public:
/**
* @brief Set the parent node.
* @param[in] rptrParent Reference to the node to assign to this node as a parent.
*/
void SetParent(const std::shared_ptr<CNode>& rptrParent);
/**
* @brief Accesses a node by its key in the parse tree.
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name.
* E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and traversed
* by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. These access
* conventions can also be chained like 'table.array[2][1].subtable.integerElement'.
* @attention Array indexing starts with 0!
* @param[in] rssPath The path of the node to searched for.
* @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found.
*/
virtual std::shared_ptr<CNode> GetDirect(const std::string& rssPath) const;
/**
* @brief Create the TOML text based on the content using an optional parent node.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @return The string containing the TOML text.
*/
std::string CreateTOMLText(const std::string& rssParent = std::string()) const;
/**
* @brief Get the TOML text based on the content.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
* different table was printed in between.
* @param[in] bFirst When set, this is the first entry in an array or table.
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
* @param[in] bAssignment When set, this is a table assignment.
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
* @return The string containing the TOML text.
*/
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst = true,
bool bEmbedded = false, bool bAssignment = true, bool bRoot = false) const = 0;
private:
std::weak_ptr<CNode> m_ptrParent; ///< Weak pointer to the parent node (if existing).
std::string m_ssName; ///< Name of the node.
public:
/**
* @brief Searches the subtree of the node for a node at the given location using the provided path.
* @remarks The path elements of arrays and tables are separated by a dot.
* @param[in] rssPath Reference to the string containing the path of the node to find.
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
*/
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const;
/**
* @brief Adds a given node to a given path in the tree
* @remarks The path elements of arrays and tables are separated by a dot.
* @param[in] rssPath Reference to the string containing the path in the tree of the location to the new node to be inserted.
* @param[in] rptrNode Reference to the smart pointer containing the new node to be added.
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is defined explicitly.
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add children.
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
* be added is already defined explicitly
*/
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly = true);
};
/**
* @brief Boolean value node.
*/
class CBooleanNode : public CNode
{
public:
/**
* @brief Constructor
* @param[in] rssName Reference to the string containing the name of the node.
* @param[in] bVal The value to assign.
*/
CBooleanNode(const std::string& rssName, bool bVal);
/**
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
* @return Type of the node.
*/
virtual sdv::toml::ENodeType GetType() const override;
/**
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
* returns empty.
*/
virtual sdv::any_t GetValue() const override;
/**
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
* different table was printed in between.
* @param[in] bFirst When set, this is the first entry in an array or table.
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
* @param[in] bAssignment When set, this is a table assignment.
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
* @return The string containing the TOML text.
*/
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool bRoot) const override;
private:
bool m_bVal; ///< Value in case of boolean node.
};
/**
* @brief Integer value node.
*/
class CIntegerNode : public CNode
{
public:
/**
* @brief Constructor
* @param[in] rssName Reference to the string containing the name of the node.
* @param[in] iVal The value to assign.
*/
CIntegerNode(const std::string& rssName, int64_t iVal);
/**
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
* @return Type of the node.
*/
virtual sdv::toml::ENodeType GetType() const override;
/**
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
* returns empty.
*/
virtual sdv::any_t GetValue() const override;
/**
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
* different table was printed in between.
* @param[in] bFirst When set, this is the first entry in an array or table.
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
* @param[in] bAssignment When set, this is a table assignment.
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
* @return The string containing the TOML text.
*/
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool bRoot) const override;
private:
int64_t m_iVal; ///< Value in case of integer node.
};
/**
* @brief Floating point value node.
*/
class CFloatingPointNode : public CNode
{
public:
/**
* @brief Constructor
* @param[in] rssName Reference to the string containing the name of the node.
* @param[in] dVal The value to assign.
*/
CFloatingPointNode(const std::string& rssName, double dVal);
/**
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
* @return Type of the node.
*/
virtual sdv::toml::ENodeType GetType() const override;
/**
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
* returns empty.
*/
virtual sdv::any_t GetValue() const override;
/**
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
* different table was printed in between.
* @param[in] bFirst When set, this is the first entry in an array or table.
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
* @param[in] bAssignment When set, this is a table assignment.
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
* @return The string containing the TOML text.
*/
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool bRoot) const override;
private:
double m_dVal; ///< Value in case of floating point node.
};
/**
* @brief String value node.
*/
class CStringNode : public CNode
{
public:
/**
* @brief Constructor
* @param[in] rssName Reference to the string containing the name of the node.
* @param[in] rssVal The value to assign.
*/
CStringNode(const std::string& rssName, const std::string& rssVal);
/**
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
* @return Type of the node.
*/
virtual sdv::toml::ENodeType GetType() const override;
/**
* @brief The node value. Overload of sdv::toml::INodeInfo::GetValue.
* @return For boolean, integer, floating point and strings, the function returns a value. Otherwise the function
* returns empty.
*/
virtual sdv::any_t GetValue() const override;
/**
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
* different table was printed in between.
* @param[in] bFirst When set, this is the first entry in an array or table.
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
* @param[in] bAssignment When set, this is a table assignment.
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
* @return The string containing the TOML text.
*/
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool bRoot) const override;
private:
std::string m_ssVal; ///< Value in case of string or illegal (error) node.
};
/**
* @brief Base structure for arrays and tables.
*/
class CNodeCollection : public CNode, public sdv::toml::INodeCollection
{
protected:
/**
* @brief Constructor
* @param[in] rssName Reference to the name of the node.
*/
CNodeCollection(const std::string& rssName);
public:
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::toml::INodeCollection)
SDV_INTERFACE_CHAIN_BASE(CNode)
END_SDV_INTERFACE_MAP()
/**
* @brief Returns the amount of nodes. Overload of sdv::toml::INodeCollection::GetCount.
* @return The amount of nodes.
*/
virtual uint32_t GetCount() const override;
/**
* @brief Get the node. Overload of sdv::toml::INodeCollection::GetNode.
* @param[in] uiIndex Index of the node to get.
* @return Interface to the node object.
*/
virtual IInterfaceAccess* GetNode(/*in*/ uint32_t uiIndex) const override;
/**
* @brief Get the node.
* @param[in] uiIndex Index of the node to get.
* @return Smart pointer to the node object.
*/
std::shared_ptr<CNode> Get(uint32_t uiIndex) const;
/**
* @brief Searches a node by its key in the parse tree
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child
* name. E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be
* accessed and traversed by using the index number in brackets. E.g. 'array[3]' would access the fourth element of
* the array 'array'. These access conventions can also be chained like 'table.array[2][1].subtable.integerElement'.
* @attention Array indexing starts with 0!
* @param[in] ssPath The path of the node to searched for.
* @return Returns an interface the requested node if available.
*/
virtual sdv::IInterfaceAccess* GetNodeDirect(/*in*/ const sdv::u8string& ssPath) const override;
/**
* @brief Add an element to the collection.
* @param[in] rptrNode Reference to the node element smart pointer.
* @param[in] bUnique When set, check prevents adding an element with the same name.
* @return Returns whether the element addition was successful.
*/
bool AddElement(const std::shared_ptr<CNode>& rptrNode, bool bUnique = false);
private:
std::vector<std::shared_ptr<CNode>> m_vecContent; ///< Vector holding the child elements
public:
bool m_bDefinedExplicitly = true; ///< WHen set, the array/table is defined explicitly
///< (not internal).
};
/**
* @brief A dynamic table structure that allows mixed data in form of key value pairs
*/
class CTable : public CNodeCollection
{
protected:
/**
* @brief Constructor
* @param[in] rssName Reference to the name of the node.
*/
CTable(const std::string& rssName);
public:
/**
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
* @return Type of the node.
*/
virtual sdv::toml::ENodeType GetType() const override;
/**
* @brief Accesses a node by its key in the parse tree. Overload of CNode::GetDirect.
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name.
* E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and traversed
* by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. These access
* conventions can also be chained like 'table.array[2][1].subtable.integerElement'.
* @attention Array indexing starts with 0!
* @param[in] rssPath The path of the node to searched for.
* @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found.
*/
virtual std::shared_ptr<CNode> GetDirect(const std::string& rssPath) const override;
/**
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
* different table was printed in between.
* @param[in] bFirst When set, this is the first entry in an array or table.
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
* @param[in] bAssignment When set, this is a table assignment.
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
* @return The string containing the TOML text.
*/
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool bRoot) const override;
/**
* @brief Adds a given node to a given path in the tree. Overload of CNode::Add.
* @param[in] rssPath Reference to the path in the tree where the new node is to be added
* @param[in] rptrNode Reference to the smart pointer holding the node.
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is
* defined explicitly
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add
* children
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
* be added is already defined explicitly
*/
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly) override;
/**
* @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find.
* @remarks The path elements of arrays and tables are separated by a dot.
* @param[in] rssPath Reference to the path in the tree where the new node is to be added
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
*/
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const override;
bool m_bOpenToAddChildren = true; ///< If internal table, the table can be extended until the table
///< is closed.
};
/**
* @brief A dynamic array structure that allows mixed data
* @details The definition of an array in TOML differentiate massively from the syntax to access the elements. For example an array
* in TOML could be defined by:
* @code
* integers = [ 1, 2, 3 ]
* nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ]
* [[products]]
* name = "Hammer"
* sku = 738594937
* @endcode
* The first two examples define the complete array at once. The third example defines one element to be added to an array. Random
* access to previous definitions is not required.
* The access functions need random access to each element. The GetDirect function uses the syntax similar to C++:
* @code
* integers[1] --> gives: 2
* nested_mixed_array[1][2] --> gives: "c"
* products[0].sku --> gives: 738594937
* @endcode
* To find array elements, the path names are composed of elements separated by a dot. The Add and Find functions use the following
* syntax:
* @code
* integers.1 --> stores: 2
* nested_mixed_array.1.2 --> stores: "c"
* products.0.sku --> stores: 738594937
* @endcode
*/
class CArray : public CNodeCollection
{
protected:
/**
* @brief Constructor
* @param[in] rssName Reference to the name of the node.
*/
CArray(const std::string& rssName);
public:
/**
* @brief Get the node type. Overload of sdv::toml::INodeInfo::GetType.
* @return Type of the node.
*/
virtual sdv::toml::ENodeType GetType() const override;
/**
* @brief Accesses a node by its key in the parse tree. Overload of CNode::GetDirect.
* @details Elements of tables can be accessed and traversed by using '.' to separated the parent name from child name.
* E.g. 'parent.child' would access the 'child' element of the 'parent' table. Elements of arrays can be accessed and traversed
* by using the index number in brackets. E.g. 'array[3]' would access the fourth element of the array 'array'. These access
* conventions can also be chained like 'table.array[2][1].subtable.integerElement'.
* @attention Array indexing starts with 0!
* @param[in] rssPath Reference to the path of the node to searched for.
* @return Returns a shared pointer to the wanted Node if it was found or a node with invalid content if it was not found.
*/
virtual std::shared_ptr<CNode> GetDirect(const std::string& rssPath) const override;
/**
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
* different table was printed in between.
* @param[in] bFirst When set, this is the first entry in an array or table.
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
* @param[in] bAssignment When set, this is a table assignment.
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
* @return The string containing the TOML text.
*/
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool bRoot) const override;
/**
* @brief Adds a given node to a given path in the tree. Overload of CNode::Add.
* @param[in] rssPath Reference to the path in the tree where the new node is to be added.
* @param[in] rptrNode Reference top the smart pointer holding the node.
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is
* defined explicitly
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add
* children
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
* be added is already defined explicitly
*/
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly) override;
/**
* @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find.
* @remarks The path elements of arrays and tables are separated by a dot.
* @param[in] rssPath Reference to the path of the node to find.
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
*/
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const override;
};
/**
* @brief Normal table
*/
class CNormalTable : public CTable
{
public:
/**
* @brief Constructor
* @param[in] rssName Reference to the name of the node.
*/
CNormalTable(const std::string& rssName) : CTable(rssName) {}
};
/**
* @brief Inline table
*/
class CInlineTable : public CTable
{
public:
/**
* @brief Constructor
* @param[in] rssName Reference to the name of the node.
*/
CInlineTable(const std::string& rssName) : CTable(rssName) {}
};
/**
* @brief Normal array
*/
class CNormalArray : public CArray
{
public:
/**
* @brief Constructor
* @param[in] rssName Reference to the name of the node.
*/
CNormalArray(const std::string& rssName) : CArray(rssName) {}
};
/**
* @brief Array of tables
*/
class CTableArray : public CArray
{
public:
/**
* @brief Constructor
* @param[in] rssName Reference to the name of the node.
*/
CTableArray(const std::string& rssName) : CArray(rssName) {}
/**
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
* different table was printed in between.
* @param[in] bFirst When set, this is the first entry in an array or table.
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
* @param[in] bAssignment When set, this is a table assignment.
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
* @return The string containing the TOML text.
*/
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool bRoot) const override;
/**
* @brief Adds a given node to a given path in the tree. Overload of CNode::Add.
* @param[in] rssPath Reference to the path in the tree where the new node is to be added.
* @param[in] rptrNode Reference top the smart pointer holding the node.
* @param[in] bDefinedExplicitly If a table that is created to create the path of the node to be added is
* defined explicitly
* @throw XInvalidAccessException Throws an XInvalidAccessException if a ancestor node is not open to add
* children
* @throw XDuplicateNameException Throws a XDuplicateNameException if a node with the same path as the node to
* be added is already defined explicitly
*/
virtual void Add(const std::string& rssPath, const std::shared_ptr<CNode>& rptrNode, bool bDefinedExplicitly) override;
/**
* @brief Searches the subtree of the node for a node at the given location using the provided path. Overload of CNode::Find.
* @remarks The path elements of arrays and tables are separated by a dot.
* @param[in] rssPath Reference to the string containing the path of the node to find.
* @return Returns a shared pointer to the wanted Node if it is found or an error-Node if it is not
*/
virtual std::shared_ptr<CNode> Find(const std::string& rssPath) const override;
};
/**
* @brief Root table
*/
class CRootTable : public CNormalTable
{
public:
/**
* @brief Constructor
*/
CRootTable() : CNormalTable("root") {}
/**
* @brief Get the TOML text based on the content. Overload of CNode::CreateTOMLText.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @param[in] rssLastPrintedTable Reference to the string containing the last printed table. This might be necessary in case a
* different table was printed in between.
* @param[in] bFirst When set, this is the first entry in an array or table.
* @param[in] bEmbedded When set, this is an embedded definition in an array or table.
* @param[in] bAssignment When set, this is a table assignment.
* @param[in] bRoot Only for table entries, when set this is the root entry (suppress the table name).
* @return The string containing the TOML text.
*/
virtual std::string CreateTOMLText(const std::string& rssParent, std::string& rssLastPrintedTable, bool bFirst, bool bEmbedded,
bool bAssignment, bool bRoot) const override;
};
#endif // !defined PARSER_NODE_TOML_H

View File

@@ -0,0 +1,380 @@
#include "parser_toml.h"
#include <iostream>
#include "exception.h"
CParserTOML::CParserTOML(const std::string& rssString) : m_lexer(rssString)
{
Process(rssString);
}
void CParserTOML::Clear()
{
m_ptrRoot = std::make_shared<CRootTable>();
m_ssCurrentTable.clear();
m_lexer.Reset();
while (!m_stackEnvironment.empty()) m_stackEnvironment.pop();
}
bool CParserTOML::Process(/*in*/ const sdv::u8string& ssContent)
{
Clear();
m_lexer.Feed(ssContent);
try
{
// Run through all tokens of the lexer and process the tokens.
while (!m_lexer.IsEnd())
{
CLexerTOML::SToken current = m_lexer.Peek();
switch (current.eCategory)
{
case CLexerTOML::ETokenCategory::token_syntax_table_open:
ProcessTable();
break;
case CLexerTOML::ETokenCategory::token_syntax_table_array_open:
ProcessTableArray();
break;
case CLexerTOML::ETokenCategory::token_key:
ProcessValueKey();
break;
case CLexerTOML::ETokenCategory::token_syntax_new_line:
m_lexer.Consume();
break;
case CLexerTOML::ETokenCategory::token_terminated:
case CLexerTOML::ETokenCategory::token_error:
throw XTOMLParseException(current.ssContentString);
break;
default:
throw XTOMLParseException("Invalid Syntax; not a Key, Table or Tablearray");
}
}
}
catch (const sdv::toml::XTOMLParseException& e)
{
std::cout << e.what() << '\n';
throw;
}
return true;
}
const CNodeCollection& CParserTOML::GetRoot() const
{
auto ptrCollection = m_ptrRoot->GetTable();
return *ptrCollection.get();
}
CNodeCollection& CParserTOML::GetRoot()
{
auto ptrCollection = m_ptrRoot->GetTable();
return *ptrCollection.get();
}
std::string CParserTOML::CreateTOMLText(const std::string& rssParent) const
{
std::string ssLastPrintedTable;
return m_ptrRoot->CreateTOMLText(rssParent, ssLastPrintedTable);
}
bool CParserTOML::Add(const std::string& rssPath, bool bVal)
{
size_t nOffset = FindLast(rssPath);
if (nOffset == std::string::npos)
nOffset = 0;
else
nOffset++;
std::string ssName = rssPath.substr(nOffset);
m_ptrRoot->Add(rssPath, std::make_shared<CBooleanNode>(ssName, bVal));
return true;
}
bool CParserTOML::Add(const std::string& rssPath, int64_t iVal)
{
size_t nOffset = FindLast(rssPath);
if (nOffset == std::string::npos)
nOffset = 0;
else
nOffset++;
std::string ssName = rssPath.substr(nOffset);
m_ptrRoot->Add(rssPath, std::make_shared<CIntegerNode>(ssName, iVal));
return true;
}
bool CParserTOML::Add(const std::string& rssPath, double dVal)
{
size_t nOffset = FindLast(rssPath);
if (nOffset == std::string::npos)
nOffset = 0;
else
nOffset++;
std::string ssName = rssPath.substr(nOffset);
m_ptrRoot->Add(rssPath, std::make_shared<CFloatingPointNode>(ssName, dVal));
return true;
}
bool CParserTOML::Add(const std::string& rssPath, const std::string& rssVal)
{
size_t nOffset = FindLast(rssPath);
if (nOffset == std::string::npos)
nOffset = 0;
else
nOffset++;
std::string ssName = rssPath.substr(nOffset);
m_ptrRoot->Add(rssPath, std::make_shared<CStringNode>(ssName, rssVal));
return true;
}
void CParserTOML::ProcessTable()
{
// Get the table path (table name preceded by parent tables separated with dots).
m_lexer.Consume();
std::string ssPath = ComposePath();
CLexerTOML::SToken sToken = m_lexer.Consume();
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_table_close)
{
throw XTOMLParseException("invalid Table construct");
}
// Find the last dot - the name follows
size_t nOffset = FindLast(ssPath);
if (nOffset == std::string::npos)
nOffset = 0; // No dot found, the whole path is one table name
else
nOffset++; // Skip the dot
std::string ssName = ssPath.substr(nOffset);
// Add the table to the root
m_ptrRoot->Add(ssPath, std::make_shared<CNormalTable>(ssName), false);
m_ssCurrentTable = ssPath;
}
void CParserTOML::ProcessTableArray()
{
m_lexer.Consume();
std::string rssKeyPath = ComposePath();
auto ptrNode = m_ptrRoot->Find(rssKeyPath);
if (!ptrNode)
{
Add<CTableArray>(rssKeyPath);
ptrNode = m_ptrRoot->Find(rssKeyPath);
}
if (!ptrNode) return;
if (dynamic_cast<CTableArray*>(ptrNode.get()))
ptrNode->GetArray()->AddElement(std::make_shared<CNormalTable>(""));
else
throw XTOMLParseException(("'" + rssKeyPath + "' already defined as a non-table-array").c_str());
m_ssCurrentTable = rssKeyPath;
CLexerTOML::SToken sToken = m_lexer.Consume();
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_table_array_close)
{
throw XTOMLParseException("invalid Table Array construct");
}
}
void CParserTOML::ProcessValueKey()
{
std::string rssKeyPath = (m_ssCurrentTable.empty() ? "" : (m_ssCurrentTable + ".")) + ComposePath();
CLexerTOML::SToken sToken = m_lexer.Consume();
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_assignment)
{
throw XTOMLParseException("Assignment expected");
}
ProcessValue(rssKeyPath);
}
void CParserTOML::ProcessValue(const std::string& rssKeyPath)
{
CLexerTOML::SToken assignmentValue = m_lexer.Consume();
switch (assignmentValue.eCategory)
{
case CLexerTOML::ETokenCategory::token_boolean:
Add(rssKeyPath, assignmentValue.bContentBoolean);
break;
case CLexerTOML::ETokenCategory::token_integer:
Add(rssKeyPath, assignmentValue.iContentInteger);
break;
case CLexerTOML::ETokenCategory::token_float:
Add(rssKeyPath, assignmentValue.dContentFloatingpoint);
break;
case CLexerTOML::ETokenCategory::token_string:
Add(rssKeyPath, assignmentValue.ssContentString);
break;
case CLexerTOML::ETokenCategory::token_syntax_array_open:
Add<CNormalArray>(rssKeyPath);
m_stackEnvironment.push(EEnvironment::env_array);
ProcessArray(rssKeyPath);
m_stackEnvironment.pop();
break;
case CLexerTOML::ETokenCategory::token_syntax_inline_table_open:
Add<CInlineTable>(rssKeyPath);
m_stackEnvironment.push(EEnvironment::env_inline_table);
ProcessInlineTable(rssKeyPath);
m_stackEnvironment.pop();
break;
default:
throw XTOMLParseException("Missing value");
break;
}
CLexerTOML::SToken sToken = m_lexer.Peek();
if (!m_stackEnvironment.empty())
{
switch (m_stackEnvironment.top())
{
case EEnvironment::env_array:
{
int32_t index = 2;
while (sToken.eCategory == CLexerTOML::ETokenCategory::token_syntax_new_line)
{
sToken = m_lexer.Peek(index++);
}
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_comma
&& sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_array_close)
{
throw XTOMLParseException(
"Invalid Token after value assignment in array; ',' or ']' needed");
}
}
break;
case EEnvironment::env_inline_table:
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_comma
&& sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_inline_table_close)
{
throw XTOMLParseException(
"Invalid Token after value assignment in inline table; ',' or '}' needed ");
}
break;
default:
break;
}
}
else
{
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_new_line && sToken.eCategory != CLexerTOML::ETokenCategory::token_eof)
{
throw XTOMLParseException("Invalid Token after value assignment; newline needed");
}
}
}
void CParserTOML::ProcessArray(const std::string& rssKeyPath)
{
/*
Arrays are defined as follow: array_name = [value, value, ...]
Arrays can have new-lines between their values.
And can end with a comma.
For example:
integers = [ 1, 2, 3 ]
colors = [ "red", "yellow", "green", ]
nested_arrays_of_ints = [ [ 1, 2 ],
[3, 4, 5] ]
nested_mixed_array = [ [ 1, 2 ], ["a", "b", "c"] ]
string_array = [ "all", 'strings', """are the same""", '''type''' ]
*/
CLexerTOML::SToken sToken = m_lexer.Peek();
size_t nIndex = 0;
enum class EExpect {value_comma_end, comma_end} eExpect = EExpect::value_comma_end;
while (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_array_close)
{
switch (sToken.eCategory)
{
//case CLexerTOML::ETokenCategory::token_syntax_array_open: // Embedded array
// if (eExpect == comma_end) throw XTOMLParseException("Expecting comma or table end.");
// m_lexer.Consume();
// ProcessArray(rssKeyPath + "." + std::to_string(nIndex++));
// eExpect = comma_end;
// break;
case CLexerTOML::ETokenCategory::token_syntax_new_line:
m_lexer.Consume();
break;
case CLexerTOML::ETokenCategory::token_syntax_comma:
m_lexer.Consume();
eExpect = EExpect::value_comma_end;
break;
default:
if (eExpect == EExpect::comma_end)
throw XTOMLParseException("Expecting comma or table end.");
ProcessValue(rssKeyPath + "." + std::to_string(nIndex++));
eExpect = EExpect::comma_end;
break;
}
sToken = m_lexer.Peek();
}
m_lexer.Consume();
}
void CParserTOML::ProcessInlineTable(const std::string& rssKeyPath)
{
/*
Inline tables are defined as follow: table_name = {value, value, ...}
For example:
name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
animal = { type.name = "pug" }
*/
CLexerTOML::SToken sToken = m_lexer.Peek();
std::string ssCurrentTableTemp = m_ssCurrentTable;
m_ssCurrentTable = rssKeyPath;
enum class EExpect { value_comma_end, value, comma_end } eExpect = EExpect::value_comma_end;
while (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_inline_table_close)
{
switch (sToken.eCategory)
{
case CLexerTOML::ETokenCategory::token_syntax_new_line:
throw XTOMLParseException("No newlines allowed in inline table");
break;
case CLexerTOML::ETokenCategory::token_syntax_comma:
if (eExpect == EExpect::value)
throw XTOMLParseException("Unexpected comma.");
m_lexer.Consume();
eExpect = EExpect::value;
break;
default:
if (eExpect == EExpect::comma_end)
throw XTOMLParseException("Expecting comma or table end.");
ProcessValueKey();
eExpect = EExpect::comma_end;
break;
}
sToken = m_lexer.Peek();
}
if (eExpect == EExpect::value)
throw XTOMLParseException("Expecting a value before inline table end.");
m_ptrRoot->Find(rssKeyPath)->GetTable()->m_bOpenToAddChildren = false;
m_lexer.Consume();
m_ssCurrentTable = ssCurrentTableTemp;
}
std::string CParserTOML::ComposePath()
{
std::string ssPath;
CLexerTOML::SToken sToken = m_lexer.Peek();
if (sToken.eCategory != CLexerTOML::ETokenCategory::token_syntax_dot
&& sToken.eCategory != CLexerTOML::ETokenCategory::token_key)
throw XTOMLParseException("Invalid Token to assemble path from keys");
while (sToken.eCategory == CLexerTOML::ETokenCategory::token_syntax_dot
|| sToken.eCategory == CLexerTOML::ETokenCategory::token_key)
{
m_lexer.Consume();
if (sToken.eCategory == CLexerTOML::ETokenCategory::token_key)
ssPath += sToken.ssContentString;
else
ssPath += ".";
sToken = m_lexer.Peek();
}
return ssPath;
}

View File

@@ -0,0 +1,173 @@
#ifndef PARSER_TOML_H
#define PARSER_TOML_H
#include "lexer_toml.h"
#include "parser_node_toml.h"
/**
* @brief Creates a tree structure from input of UTF-8 encoded TOML source data
*/
class CParserTOML : public sdv::IInterfaceAccess, public sdv::toml::ITOMLParser
{
public:
/**
* @brief Default constructor
*/
CParserTOML() = default;
/**
* @brief Construct a new Parser object
* @param[in] rssString UTF-8 encoded data of a TOML source
*/
CParserTOML(const std::string& rssString);
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::toml::ITOMLParser)
SDV_INTERFACE_CHAIN_MEMBER(m_ptrRoot)
END_SDV_INTERFACE_MAP()
/**
* @brief Clears the current parse result.
* @attention This will render any pointer invalid!
*/
void Clear();
// Ignore cppcheck warning for not using dynamic binding when being called through the constructor.
// cppcheck-suppress virtualCallInConstructor
/**
* @brief Process the configuration from the supplied content string. Overload of sdv::toml::ITOMLParser.
* @param[in] ssContent Configuration string.
* @return Returns 'true' when the configuration could be read successfully, false when not.
*/
virtual bool Process(/*in*/ const sdv::u8string& ssContent) override;
/**
* @{
* @brief Return the root node.
* @return Reference to the root node collection.
*/
const CNodeCollection& GetRoot() const;
CNodeCollection& GetRoot();
/**
* @}
*/
/**
* @brief Get the TOML text based on the content.
* @param[in] rssParent When present, uses the parent node into the TOML text generation.
* @return The string containing the TOML text.
*/
std::string CreateTOMLText(const std::string& rssParent = std::string()) const;
private:
/**
* @brief Add a collection node (table or array).
* @tparam TCollectionNode The collection node class to add (to create).
* @param[in] rssPath Reference to the node path.
* @return Returns whether the node could be added.
*/
template <class TCollectionNode>
bool Add(const std::string& rssPath);
/**
* @brief Add a boolean value node.
* @param[in] rssPath Reference to the node path.
* @param[in] bVal The boolean value.
* @return Returns whether the node could be added.
*/
bool Add(const std::string& rssPath, bool bVal);
/**
* @brief Add a integer value node.
* @param[in] rssPath Reference to the node path.
* @param[in] iVal The integer value.
* @return Returns whether the node could be added.
*/
bool Add(const std::string& rssPath, int64_t iVal);
/**
* @brief Add a floating point value node.
* @param[in] rssPath Reference to the node path.
* @param[in] dVal The floating point value.
* @return Returns whether the node could be added.
*/
bool Add(const std::string& rssPath, double dVal);
/**
* @brief Add a string value node.
* @param[in] rssPath Reference to the node path.
* @param[in] rssVal Reference to the string value.
* @return Returns whether the node could be added.
*/
bool Add(const std::string& rssPath, const std::string& rssVal);
/**
* @brief Process a table declaration.
*/
void ProcessTable();
/**
* @brief Process a table array declaration.
*/
void ProcessTableArray();
/**
* @brief Process the value key.
*/
void ProcessValueKey();
/**
* @brief Process the value with the supplied key.
* @param[in] rssKeyPath Reference to the key path string.
*/
void ProcessValue(const std::string& rssKeyPath);
/**
* @brief Process the array value with the supplied key.
* @param[in] rssKeyPath Reference to the key path string.
*/
void ProcessArray(const std::string& rssKeyPath);
/**
* @brief Process the inline table value with the supplied key.
* @param[in] rssKeyPath Reference to the key path string.
*/
void ProcessInlineTable(const std::string& rssKeyPath);
/**
* @brief Compose a path from lexer tokens. A path is composed of table and array elements separated with a dot.
* @return The composed path.
*/
std::string ComposePath();
/**
* @brief Enum for differentiating between an array environment and an inline table environment for syntax checks.
*/
enum class EEnvironment
{
env_array, //!< Environment for an array
env_inline_table //!< Environment for a table
};
std::stack<EEnvironment> m_stackEnvironment; ///< Tracking of environments in nested structures.
std::shared_ptr<CNode> m_ptrRoot = std::make_shared<CRootTable>(); ///< The one root node.
std::string m_ssCurrentTable; ///< Path to the current table.
CLexerTOML m_lexer; ///< Lexer.
};
template <class TCollectionNode>
inline bool CParserTOML::Add(const std::string& rssPath)
{
size_t nOffset = rssPath.rfind('.');
if (nOffset == std::string::npos)
nOffset = 0;
else
nOffset++;
std::string ssName = rssPath.substr(nOffset);
m_ptrRoot->Add(rssPath, std::make_shared<TCollectionNode>(ssName), true);
return true;
}
#endif // PARSER_TOML_H

View File

@@ -0,0 +1,33 @@
#ifndef CONFIG_UTILITY_H
#define CONFIG_UTILITY_H
#include <support/component_impl.h>
#include "toml_parser/parser_toml.h"
/**
* @brief Configuration utility component.
*/
class CTOMLParserUtility : public sdv::CSdvObject
{
public:
/**
* @brief Default constructor
*/
CTOMLParserUtility() = default;
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_CHAIN_MEMBER(m_parser)
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::Utility)
DECLARE_OBJECT_CLASS_NAME("TOMLParserUtility")
private:
CParserTOML m_parser; ///< Configuration parser
};
DEFINE_SDV_OBJECT_NO_EXPORT(CTOMLParserUtility)
#endif // !defined CONFIG_UTILITY_H