From fb842c27c5cf90beb36504ad4b420e45607662dc Mon Sep 17 00:00:00 2001 From: "Mueller Julian (CC-AD/EYF1)" Date: Wed, 10 Mar 2021 15:15:25 +0100 Subject: [PATCH 001/127] iox-558 Fuzz Wrappers for Iceoryx including Unix Domain Socket Fuzzing and TOML Config Parser Fuzzing Signed-off-by: Mueller Julian (CC-AD/EYF1) --- iceoryx_posh/test/CMakeLists.txt | 13 +- iceoryx_posh/test/fuzztests/README.md | 49 +++ .../test/fuzztests/fuzz_input/fuzz_input.txt | 2 + .../fuzztests/fuzztests_roudi_wrapper.cpp | 342 ++++++++++++++++++ .../fuzztests/fuzztests_roudi_wrapper.hpp | 92 +++++ 5 files changed, 497 insertions(+), 1 deletion(-) create mode 100644 iceoryx_posh/test/fuzztests/README.md create mode 100644 iceoryx_posh/test/fuzztests/fuzz_input/fuzz_input.txt create mode 100644 iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp create mode 100644 iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp diff --git a/iceoryx_posh/test/CMakeLists.txt b/iceoryx_posh/test/CMakeLists.txt index 61db7e53307..4545a0a9b74 100644 --- a/iceoryx_posh/test/CMakeLists.txt +++ b/iceoryx_posh/test/CMakeLists.txt @@ -31,7 +31,7 @@ file(GLOB_RECURSE INTEGRATIONTESTS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/integrationt file(GLOB_RECURSE COMPONENTTESTS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/componenttests/*.cpp") file(GLOB_RECURSE TESTUTILS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/testutils/*.cpp") file(GLOB_RECURSE MOCKS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/mocks/*.cpp") - +file(GLOB_RECURSE FUZZTESTS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/fuzztests/*.cpp") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_PREFIX}/test) @@ -83,3 +83,14 @@ set_target_properties(${PROJECT_PREFIX}_integrationtests PROPERTIES CXX_STANDARD ${ICEORYX_CXX_STANDARD} POSITION_INDEPENDENT_CODE ON ) + +# fuzz test +add_executable( ${PROJECT_PREFIX}_fuzztests ${FUZZTESTS_SRC} ${TESTUTILS_SRC} ${MOCKS_SRC}) +target_compile_options(${PROJECT_PREFIX}_fuzztests PRIVATE ${TEST_CXX_FLAGS}) +target_include_directories(${PROJECT_PREFIX}_fuzztests PRIVATE .) +target_link_libraries(${PROJECT_PREFIX}_fuzztests ${TEST_LINK_LIBS}) +set_target_properties(${PROJECT_PREFIX}_fuzztests PROPERTIES + CXX_STANDARD_REQUIRED ON + CXX_STANDARD ${ICEORYX_CXX_STANDARD} + POSITION_INDEPENDENT_CODE ON +) diff --git a/iceoryx_posh/test/fuzztests/README.md b/iceoryx_posh/test/fuzztests/README.md new file mode 100644 index 00000000000..97438dcfac0 --- /dev/null +++ b/iceoryx_posh/test/fuzztests/README.md @@ -0,0 +1,49 @@ +#Fuzzing Iceoryx + +##What is fuzzing? +Fuzzing is a method for dynamic code analysis which can be used to find several types of bugs which could also be security vulnerabilities. These vulnerabilities which could lead to a crash such as buffer overflow, use-after-free, dangling pointer and more can be found. +More information about fuzzing can for example be found [here](https://owasp.org/www-community/Fuzzing "OWASP Fuzzing"). + +##Why should you use fuzzing? +1. **It is easy to use** - These fuzz wrappers provide you a possibility to integrate a fuzzer very fast. It can then run for several hours or days and find bugs automatically. +2. **It is effective** - Lots of security vulnerabilities in the wild are found with fuzzers. +3. **It has a low false positive rate** - If the program crashes, then it is very likely that something in your code was wrong. + + +##How to build +First of all, you need to install the fuzzer you want to use. The american fuzzy lop (afl) for example can be found [here](https://github.com/google/AFL "Github AFL"). +For some fuzzers like the afl, the code needs to be instrumented first. For afl you can download the [LLVM](https://llvm.org/releases/download.html "LLVM"). Afterwards you need to compile the code with an afl compiler which does the instrumentation such as afl-clang-fast. + +Go to your iceoryx folder and change the compiler for example via cmake: + +_cmake -Bbuild -Hiceoryx_meta -DBUILD_TEST=True -DCMAKE_C_COMPILER=afl-clang-fast -DCMAKE_CXX_COMPILER=afl-clang-fast++_ +Afterwards you can build the project for example via: +_cmake --build build_ + +Iceoryx is now ready to be fuzzed. + +##How to start testing +The fuzz-wrapper binary can be found in [path_to_iceoryx]/iceoryx_eclipse/build/posh/test/posh_fuzztests + +To start fuzzing with afl, you need two folders: One which contains examples of valid messages which can be send to the interface and one folder where the fuzzing results are stored. There is already an example folder with a valid text message to test the uds communication. It can be found within [path_to_iceoryx]/iceoryx_posh/test/fuzztests/fuzz_input/ . + +The fuzz wrappers currently support three different interfaces: the unix domain socket communication, the message parsing method and the toml configuration file parser. +1. The unix domain socket can either be tested by starting RouDi and sending messages to RouDi via uds or by directly invoking the message parsing function within RouDi. The first solution has the adventage, that the messages from uds to RouDi take the same path as normal messages from applications would do. The drawback however is that RouDi needs to be started with all the overhead which would not be necessary for this test which therefore slows the test a little bit. + +2. By invoking the message parsing function directly, the method is independent from the underlying protocol such as uds. It should also be slightly faster since some functions are not invoked compared to uds fuzzing. However, a RouDi thread is also started with this approach because otherwise it was not be possible to invoke the message process function within RouDi without directly modifying the code in RouDi. + +3. As a third use-case, the toml configuration parser can be tested. An example of a toml file can be found here: [path_to_iceoryx]iceoryx_posh/etc/iceoryx/roudi_config_example.toml + +The interface you want to fuzz can be chosen with -f or --fuzzing-api + entering the argument: uds, com or toml. The argument uds is used for use-case 1, com for use-case 2 and toml for use-case 3. + +The way how the fuzzer sends messages to the interface can also be specified via -m or --input-mode +1. The messages can be sent via stdin (-m stdin) +2. The messages can be sent via using the command line (-m cl) followed by -i or --comand-line-input with the message following as argument. + +Additionally, the log level can be chosen via -l or --log-level such that messages are printed for debug or not. The default is to switch logging off. 3 arguments are possible: +1. off: to disable logging (standard case) +2. fatal: log fatal messages +3. debug: log debug messages + +As an example: afl can be started for uds with: afl-fuzz -i [path_to_input_example] -o [path_where_output_of_fuzzer_is_written_to] [path_to_iceoryx]/build/posh/test/posh_fuzztests -f uds -m stdin + \ No newline at end of file diff --git a/iceoryx_posh/test/fuzztests/fuzz_input/fuzz_input.txt b/iceoryx_posh/test/fuzztests/fuzz_input/fuzz_input.txt new file mode 100644 index 00000000000..89151b49b83 --- /dev/null +++ b/iceoryx_posh/test/fuzztests/fuzz_input/fuzz_input.txt @@ -0,0 +1,2 @@ +1,/test,2,1000,2,1:02:171:01:01:01:5, +3,/test,2,1000,2,1:02:171:01:01:01:5, diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp new file mode 100644 index 00000000000..64ff9ff46fa --- /dev/null +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -0,0 +1,342 @@ +// Copyright (c) 2020 by Robert Bosch GmbH. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +#include "iceoryx_posh/internal/roudi/roudi.hpp" +#include "iceoryx_posh/internal/log/posh_logging.hpp" +#include "iceoryx_posh/roudi/iceoryx_roudi_components.hpp" +#include "iceoryx_utils/platform/getopt.hpp" +#include "fuzztests_roudi_wrapper.hpp" +#include "cpptoml.h" + + +std::string const UDS_NAME = "/tmp/"; +unsigned const char TIMEOUT = 50; //5s for a Timeout +#define INTERFACE_NAME "/test" + +iox::log::LogLevel m_logLevel{iox::log::LogLevel::kOff}; + + +StringToIPCMessage::StringToIPCMessage(const iox::ProcessName_t& name, const int64_t maxMessages, const int64_t messageSize) : iox::runtime::IpcInterfaceBase(name, maxMessages, messageSize) +{ +} + +RouDiFuzz::RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, iox::roudi::PortManager& portManager, iox::roudi::RouDi::RoudiStartupParameters aStartupParameter = {iox::roudi::MonitoringMode::OFF, false, iox::roudi::RouDi::RuntimeMessagesThreadStart::IMMEDIATE, iox::version::CompatibilityCheckLevel::OFF}) : iox::roudi::RouDi(roudiMemoryInterface, portManager, aStartupParameter) +{ +} + +void RouDiFuzz::processMessageFuzz(std::string aMessage) +{ + iox::runtime::IpcMessage ipcMessage; + StringToIPCMessage::setMessageFromString(aMessage.c_str(), ipcMessage); + iox::runtime::IpcMessageType cmd = iox::runtime::stringToIpcMessageType(ipcMessage.getElementAtIndex(0).c_str()); + std::string processName = ipcMessage.getElementAtIndex(1); + iox::roudi::RouDi::processMessage(ipcMessage, cmd, iox::ProcessName_t(iox::cxx::TruncateToCapacity, processName)); +} + + +std::vector FuzzHelper::getStdInMessages() +{ + std::vector stdInMessages; + for (std::string line; std::getline(std::cin, line);) + { + stdInMessages.push_back(line); + } + return stdInMessages; +} + +std::shared_ptr FuzzHelper::startRouDiThread() +{ + static iox::roudi::IceOryxRouDiComponents m_rouDiComponents(iox::RouDiConfig_t().setDefaults()); + static iox::RouDiConfig_t m_config = iox::RouDiConfig_t().setDefaults(); + std::shared_ptr aRouDi(new RouDiFuzz(m_rouDiComponents.m_rouDiMemoryManager, m_rouDiComponents.m_portManager)); + return aRouDi; +} + +bool FuzzHelper::checkIsRouDiRunning() +{ + Fuzzing aFuzzer; + int udsStatus = aFuzzer.fuzzingRouDiUDS("Hello Roudi!"); + if (udsStatus == -1) + { + return false; + } + else + { + return true; + } +} + +Fuzzing::Fuzzing() +{ + +} + +void Fuzzing::fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage) +{ + if(aRouDi != nullptr) + { + aRouDi->processMessageFuzz(aMessage); + } + else + { + iox::LogDebug() << "Error, the Smart Pointer for RouDI which is used to call the method 'processMessage' is NULL"; + } +} + +int Fuzzing::fuzzingRouDiUDS(std::string aMessage) +{ + int sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); + struct sockaddr aSockAddr; + aSockAddr.sa_family = AF_LOCAL; + std::string roudiName = UDS_NAME + iox::roudi::IPC_CHANNEL_ROUDI_NAME; + strncpy(aSockAddr.sa_data, roudiName.c_str(), sizeof(aSockAddr.sa_data)); + int connectfd = connect(sockfd, &aSockAddr, sizeof(aSockAddr)); + if (connectfd != -1) + { + sendto(sockfd, aMessage.c_str(), aMessage.length()+1, static_cast(0), nullptr, static_cast(0)); + } + else + { + iox::LogDebug() << "Could not connect to RoudI"; + } + return connectfd; +} + + +void Fuzzing::fuzzing_TOML_parser(std::string toml_file) +{ + cpptoml::parse_file(toml_file.c_str()); +} + +CmdLineParserFuzzing::CmdLineParserFuzzing() +{ + fuzzing_API = fuzzingAPI::NONE; + input_mode = inputMode::NONE; + errorFlag = true; + cmdLineFlag = false; + helpFlag = false; + +} + +std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) noexcept +{ + constexpr option longOptions[] = {{"help", no_argument, nullptr, 'h'}, + {"fuzzing-API", required_argument, nullptr, 'f'}, + {"input-mode", required_argument, nullptr, 'm'}, + {"command-line-input", required_argument, nullptr, 'i'}, + {"log-level", required_argument, nullptr, 'l'}, + {nullptr, 0, nullptr, 0}}; + constexpr const char* shortOptions = "hf:m:i:l:"; + int32_t index; + int32_t opt{-1}; + while ((opt = getopt_long(argc, argv, shortOptions, longOptions, &index), opt != -1)) + { + errorFlag = false; + switch (opt) + { + case 'h': + { + std::cout << "Usage: " << argv[0] << " [options]" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << "-h, --help Display help." << std::endl; + std::cout << "-f, --fuzzing-api Specify API which will be fuzzed." << std::endl; + std::cout << " {uds, com, toml}" << std::endl; + std::cout << " uds: Starts RouDi and sends messages via Unix Domain Sockets. Multiple messages can be sent. (e.g.: register message first and then offer service)." << std::endl; + std::cout << " com: Invokes the processMessage method in RouDi directly. This abstracts the IPC and is faster but multiple messages are not supported." << std::endl; + std::cout << " toml: Send inputs to test the TOML config file parser." << std::endl; + std::cout << "-m, --input-mode {stdin, cl}" << std::endl; + std::cout << " stdin: Send input via stdin." << std::endl; + std::cout << " cl: Send input via commandline. Needs parameter i to send the input." << std::endl; + std::cout << "-i, --command-line-input : Send the input via this command line, requires to use input-mode cl. It's possible to send several commands with several -i commands." << std::endl; + std::cout << "-l, --log-level {off, fatal, debug} : Set the log level. Off is standard;" << std::endl; + helpFlag = true; + } break; + + case 'f': + { + if (strcmp(optarg, "uds") == 0) + { + fuzzing_API = fuzzingAPI::UDS; + } + else if (strcmp(optarg, "com") == 0) + { + fuzzing_API = fuzzingAPI::COM; + } + else if (strcmp(optarg, "toml") == 0) + { + fuzzing_API = fuzzingAPI::TOML; + } + else + { + std::cout << "Options for fuzzing-api are 'uds', 'com' and 'toml'!" << std::endl; + } + } break; + + case 'm': + { + if (strcmp(optarg, "stdin") == 0) + { + input_mode = inputMode::STDIN; + FuzzHelper aFuzzHelper; + allMessages = aFuzzHelper.getStdInMessages(); + } + else if (strcmp(optarg, "cl") == 0) + { + input_mode = inputMode::CL; + } + else + { + std::cout << "Options for input-mode are 'stdin' and 'cl'!" << std::endl; + } + } break; + case 'i': + { + cmdLineFlag = true; + allMessages.emplace_back(optarg); + + } break; + case 'l': + { + if (strcmp(optarg, "off") == 0) + { + m_logLevel = iox::log::LogLevel::kOff; + } + else if (strcmp(optarg, "fatal") == 0) + { + m_logLevel = iox::log::LogLevel::kFatal; + } + else if (strcmp(optarg, "debug") == 0) + { + m_logLevel = iox::log::LogLevel::kDebug; + } + else + { + std::cout << "Options for Logging are 'off', 'fatal' and 'debug'!" << std::endl; + } + } break; + default: + { + std::cout << "Unknown command.\n" << std::endl; + errorFlag = true; + return allMessages; + } break; + }; + } + return allMessages; +} + +int main (int argc, char* argv[]) +{ + CmdLineParserFuzzing cmd; + std::vector allMessages = cmd.parseCmd(argc, argv); + + if(cmd.helpFlag == true) + { + return 1; + } + + if(cmd.errorFlag == true) + { + std::cout << "No or wrong command lines were specified. Please use --help!" << std::endl; + return -1; + } + + if(allMessages.empty()) + { + std::cout << "Please use -m [cl, stdin] to enter the input you want to send to the executable. If you use -m cl, then you also need use -i [INPUT_MESSAGE] to specify the message." << std::endl; + return -1; + } + + if (cmd.input_mode == inputMode::NONE) + { + std::cout << "Use -m to specify the input. Please use --help to get more information." << std::endl; + return -1; + } + + if(cmd.input_mode == inputMode::CL and !cmd.cmdLineFlag) + { + std::cout << "Please, use -i to enter a String which you want to send to the interface. It is also possible to use -m stdin instead." << std::endl; + return -1; + } + iox::log::LogManager::GetLogManager().SetDefaultLogLevel(m_logLevel); + FuzzHelper aFuzzHelper; + std::shared_ptr aRouDi; + if (cmd.fuzzing_API == fuzzingAPI::UDS or cmd.fuzzing_API == fuzzingAPI::COM) // Start RouDi + { + aRouDi = aFuzzHelper.startRouDiThread(); + unsigned char timeout = 0; + while(!aFuzzHelper.checkIsRouDiRunning()) + { + if(timeout >= TIMEOUT) + { + std::cout << "RouDI could not be started, program terminates!" << std::endl; + return -1; + } + usleep(100000); //1/10 of a second + timeout += 1; + } + } + if(cmd.input_mode == inputMode::CL or cmd.input_mode == inputMode::STDIN) + { + Fuzzing aFuzzer; + for(std::string aMessage: allMessages) + { + + switch (cmd.fuzzing_API) + { + case fuzzingAPI::COM: + { + iox::LogDebug() << "Messages sent to RouDi: " << aMessage; + aFuzzer.fuzzingRouDiCom(aRouDi, aMessage); + } break; + + case fuzzingAPI::UDS: + { + aFuzzer.fuzzingRouDiUDS(aMessage); + iox::LogDebug() << "Messages sent to RouDi: " << aMessage; + } break; + + case fuzzingAPI::TOML: + { + iox::LogDebug() << "Messages sent to TOML Parser: " << aMessage; + aFuzzer.fuzzing_TOML_parser(aMessage); + } break; + + default: + { + + std::cout << "Error: Unkown Fuzzing API parameter" << std::endl; + return -1; + + } break; + }; + } + } + + else + { + std::cout << "Error: Only stdin and command line are allowed to enter an input. Please use --help to get more information." << std::endl; + return -1; + } + +} + + +//@TODO README + + + + + diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp new file mode 100644 index 00000000000..f1fde787cf3 --- /dev/null +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp @@ -0,0 +1,92 @@ +// Copyright (c) 2020 by Robert Bosch GmbH. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +enum class fuzzingAPI +{ + NONE, + UDS, + COM, + TOML +}; + +enum class inputMode +{ + NONE, + STDIN, + CL +}; + + +class StringToIPCMessage : public iox::runtime::IpcInterfaceBase +{ + public: + StringToIPCMessage(const iox::ProcessName_t& name, const int64_t maxMessages, const int64_t messageSize); + using iox::runtime::IpcInterfaceBase::setMessageFromString; +}; + + +class RouDiFuzz : iox::roudi::RouDi +{ + public: + RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, iox::roudi::PortManager& portManager, iox::roudi::RouDi::RoudiStartupParameters aStartupParameter); + void processMessageFuzz( std::string aMessage); +}; + +class FuzzHelper +{ + + public: + std::vector getStdInMessages(); + std::shared_ptr startRouDiThread(); + bool checkIsRouDiRunning(); + + +}; + +class Fuzzing +{ + + public: + Fuzzing(); + void fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage); + int fuzzingRouDiUDS(std::string aMessage); + void fuzzing_TOML_parser(std::string toml_file); +}; + + +class CmdLineParserFuzzing +{ + public: + + CmdLineParserFuzzing(); + std::vector parseCmd(int argc, char* argv[]) noexcept; + + fuzzingAPI fuzzing_API; + inputMode input_mode; + bool errorFlag; + bool cmdLineFlag; + bool helpFlag; + std::vector allMessages; + }; + + +int main (int argc, char* argv[]); + + + + + + + From e46b7a6010200c0be3c33246fb7e61ad04e4055a Mon Sep 17 00:00:00 2001 From: "Mueller Julian (CC-AD/EYF1)" Date: Thu, 11 Mar 2021 17:00:52 +0100 Subject: [PATCH 002/127] iox-558 Fixed a bug where the TOML configuration parser was not fuzzed correctly. Signed-off-by: Mueller Julian (CC-AD/EYF1) --- iceoryx_posh/test/fuzztests/README.md | 12 +- .../fuzztests/fuzztests_roudi_wrapper.cpp | 116 ++++++++++++++---- .../fuzztests/fuzztests_roudi_wrapper.hpp | 5 +- 3 files changed, 103 insertions(+), 30 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/README.md b/iceoryx_posh/test/fuzztests/README.md index 97438dcfac0..0c5f8e5dab9 100644 --- a/iceoryx_posh/test/fuzztests/README.md +++ b/iceoryx_posh/test/fuzztests/README.md @@ -16,34 +16,34 @@ For some fuzzers like the afl, the code needs to be instrumented first. For afl Go to your iceoryx folder and change the compiler for example via cmake: -_cmake -Bbuild -Hiceoryx_meta -DBUILD_TEST=True -DCMAKE_C_COMPILER=afl-clang-fast -DCMAKE_CXX_COMPILER=afl-clang-fast++_ +_cmake -Bbuild -Hiceoryx\_meta -DBUILD\_TEST=True -DCMAKE\_C\_COMPILER=afl-clang-fast -DCMAKE\_CXX\_COMPILER=afl-clang-fast++_ Afterwards you can build the project for example via: _cmake --build build_ Iceoryx is now ready to be fuzzed. ##How to start testing -The fuzz-wrapper binary can be found in [path_to_iceoryx]/iceoryx_eclipse/build/posh/test/posh_fuzztests +The fuzz-wrapper binary can be found in \[path\_to\_iceoryx\]/iceoryx\_eclipse/build/posh/test/posh\_fuzztests -To start fuzzing with afl, you need two folders: One which contains examples of valid messages which can be send to the interface and one folder where the fuzzing results are stored. There is already an example folder with a valid text message to test the uds communication. It can be found within [path_to_iceoryx]/iceoryx_posh/test/fuzztests/fuzz_input/ . +To start fuzzing with afl, you need two folders: One which contains examples of valid messages which can be send to the interface and one folder where the fuzzing results are stored. There is already an example folder with a valid text message to test the uds communication. It can be found within \[path\_to\_iceoryx\]/iceoryx\_posh/test/fuzztests/fuzz\_input/ . The fuzz wrappers currently support three different interfaces: the unix domain socket communication, the message parsing method and the toml configuration file parser. 1. The unix domain socket can either be tested by starting RouDi and sending messages to RouDi via uds or by directly invoking the message parsing function within RouDi. The first solution has the adventage, that the messages from uds to RouDi take the same path as normal messages from applications would do. The drawback however is that RouDi needs to be started with all the overhead which would not be necessary for this test which therefore slows the test a little bit. 2. By invoking the message parsing function directly, the method is independent from the underlying protocol such as uds. It should also be slightly faster since some functions are not invoked compared to uds fuzzing. However, a RouDi thread is also started with this approach because otherwise it was not be possible to invoke the message process function within RouDi without directly modifying the code in RouDi. -3. As a third use-case, the toml configuration parser can be tested. An example of a toml file can be found here: [path_to_iceoryx]iceoryx_posh/etc/iceoryx/roudi_config_example.toml +3. As a third use-case, the toml configuration parser can be tested. An example of a toml file can be found here: \[path\_to\_iceoryx\]/iceoryx\_posh/etc/iceoryx/roudi\_config\_example.toml. If you chose this method, you also need to set -t or --toml-file . A file needs to be specified which can be used to write in the messages which will send to the toml configuration parser. The interface you want to fuzz can be chosen with -f or --fuzzing-api + entering the argument: uds, com or toml. The argument uds is used for use-case 1, com for use-case 2 and toml for use-case 3. The way how the fuzzer sends messages to the interface can also be specified via -m or --input-mode 1. The messages can be sent via stdin (-m stdin) -2. The messages can be sent via using the command line (-m cl) followed by -i or --comand-line-input with the message following as argument. +2. The messages can be sent via using the command line (-m cl) followed by -i or --comand-line-input with the message following as argument or -c --command-line-file to specify a file where the message you want to send is located. By using -c or --command-line-file, the complete file is sent as one message and is not sent line by line. Additionally, the log level can be chosen via -l or --log-level such that messages are printed for debug or not. The default is to switch logging off. 3 arguments are possible: 1. off: to disable logging (standard case) 2. fatal: log fatal messages 3. debug: log debug messages -As an example: afl can be started for uds with: afl-fuzz -i [path_to_input_example] -o [path_where_output_of_fuzzer_is_written_to] [path_to_iceoryx]/build/posh/test/posh_fuzztests -f uds -m stdin +As an example: afl can be started for uds with: afl-fuzz -i \[path\_to\_input\_example\] -o \[path\_where\_output\_of\_fuzzer\_is\_written\_to\] \[path\_to\_iceoryx\]/build/posh/test/posh\_fuzztests -f uds -m stdin \ No newline at end of file diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index 64ff9ff46fa..1827ed16e90 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -19,12 +19,14 @@ #include "iceoryx_utils/platform/getopt.hpp" #include "fuzztests_roudi_wrapper.hpp" #include "cpptoml.h" - +#include +#include std::string const UDS_NAME = "/tmp/"; unsigned const char TIMEOUT = 50; //5s for a Timeout #define INTERFACE_NAME "/test" + iox::log::LogLevel m_logLevel{iox::log::LogLevel::kOff}; @@ -78,6 +80,19 @@ bool FuzzHelper::checkIsRouDiRunning() } } +std::vector FuzzHelper::combineString(std::vector allMessages) +{ + std::string tempString = ""; + for(std::string aMessage: allMessages) + { + tempString += aMessage + "\n"; + } + allMessages.clear(); + allMessages.emplace_back(tempString); + return allMessages; +} + + Fuzzing::Fuzzing() { @@ -115,9 +130,21 @@ int Fuzzing::fuzzingRouDiUDS(std::string aMessage) } -void Fuzzing::fuzzing_TOML_parser(std::string toml_file) +void Fuzzing::fuzzing_TOML_parser(std::string aMessage, std::string tempFile) { - cpptoml::parse_file(toml_file.c_str()); + std::ofstream aTomlFile; + aTomlFile.open(tempFile); + std::cout << "Sent to TOML: " << aMessage << std::endl; + if(aTomlFile.is_open()) + { + aTomlFile << aMessage; + cpptoml::parse_file(tempFile); + aTomlFile.close(); + } + else + { + iox::LogDebug() << "Cannot open file to send it to TOML Parser: " << tempFile; + } } CmdLineParserFuzzing::CmdLineParserFuzzing() @@ -127,6 +154,8 @@ CmdLineParserFuzzing::CmdLineParserFuzzing() errorFlag = true; cmdLineFlag = false; helpFlag = false; + tomlFileFlag = false; + tomlFile = ""; } @@ -135,10 +164,12 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) constexpr option longOptions[] = {{"help", no_argument, nullptr, 'h'}, {"fuzzing-API", required_argument, nullptr, 'f'}, {"input-mode", required_argument, nullptr, 'm'}, + {"command-line-file", required_argument, nullptr, 'c'}, {"command-line-input", required_argument, nullptr, 'i'}, + {"toml-file", required_argument, nullptr, 't'}, {"log-level", required_argument, nullptr, 'l'}, {nullptr, 0, nullptr, 0}}; - constexpr const char* shortOptions = "hf:m:i:l:"; + constexpr const char* shortOptions = "hf:m:i:l:c:t:"; int32_t index; int32_t opt{-1}; while ((opt = getopt_long(argc, argv, shortOptions, longOptions, &index), opt != -1)) @@ -150,17 +181,19 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) { std::cout << "Usage: " << argv[0] << " [options]" << std::endl; std::cout << "Options:" << std::endl; - std::cout << "-h, --help Display help." << std::endl; - std::cout << "-f, --fuzzing-api Specify API which will be fuzzed." << std::endl; - std::cout << " {uds, com, toml}" << std::endl; - std::cout << " uds: Starts RouDi and sends messages via Unix Domain Sockets. Multiple messages can be sent. (e.g.: register message first and then offer service)." << std::endl; - std::cout << " com: Invokes the processMessage method in RouDi directly. This abstracts the IPC and is faster but multiple messages are not supported." << std::endl; - std::cout << " toml: Send inputs to test the TOML config file parser." << std::endl; - std::cout << "-m, --input-mode {stdin, cl}" << std::endl; - std::cout << " stdin: Send input via stdin." << std::endl; - std::cout << " cl: Send input via commandline. Needs parameter i to send the input." << std::endl; - std::cout << "-i, --command-line-input : Send the input via this command line, requires to use input-mode cl. It's possible to send several commands with several -i commands." << std::endl; - std::cout << "-l, --log-level {off, fatal, debug} : Set the log level. Off is standard;" << std::endl; + std::cout << "-h, --help Display help." << std::endl; + std::cout << "-f, --fuzzing-api Specify API which will be fuzzed." << std::endl; + std::cout << " {uds, com, toml}" << std::endl; + std::cout << " uds: Starts RouDi and sends messages via Unix Domain Sockets. Multiple messages can be sent. (e.g.: register message first and then offer service)." << std::endl; + std::cout << " com: Invokes the processMessage method in RouDi directly. This abstracts the IPC and is faster but multiple messages are not supported." << std::endl; + std::cout << " toml: Send inputs to test the TOML config file parser. A file is created in your current working directory and the path is sent to the Parser." << std::endl; + std::cout << "-m, --input-mode {stdin, cl}" << std::endl; + std::cout << " stdin: Send input via stdin." << std::endl; + std::cout << " cl: Send input via commandline. Needs parameter i to send the input." << std::endl; + std::cout << "-c, --command-line-file : Read the specified file and send the input to the interface." << std::endl; + std::cout << "-i, --command-line-input : Send the input via this command line, requires to use input-mode cl. It's possible to send several commands with several -i commands." << std::endl; + std::cout << "-t, --toml-file : Needs to be used when TOML is parsed. The file is used to write messages which will be parsed by the TOML configuration parser." << std::endl; + std::cout << "-l, --log-level {off, fatal, debug} : Set the log level. Off is standard;" << std::endl; helpFlag = true; } break; @@ -207,6 +240,26 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) allMessages.emplace_back(optarg); } break; + + case 'c': + { + cmdLineFlag = true; + std::ifstream ifile; + ifile.open(optarg); + if(ifile) + { + std::string tempFileContent( (std::istreambuf_iterator(ifile) ), (std::istreambuf_iterator())); + allMessages.emplace_back(tempFileContent); + } + + else + { + std::cout<<"Error cannot open file. Either file does not exist or I don't have the permissions to open it."; + errorFlag = true; + return allMessages; + } + }break; + case 'l': { if (strcmp(optarg, "off") == 0) @@ -226,6 +279,11 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) std::cout << "Options for Logging are 'off', 'fatal' and 'debug'!" << std::endl; } } break; + case 't': + { + tomlFileFlag = true; + tomlFile = optarg; + } break; default: { std::cout << "Unknown command.\n" << std::endl; @@ -255,24 +313,38 @@ int main (int argc, char* argv[]) if(allMessages.empty()) { - std::cout << "Please use -m [cl, stdin] to enter the input you want to send to the executable. If you use -m cl, then you also need use -i [INPUT_MESSAGE] to specify the message." << std::endl; + std::cout << "Please use -m [cl, stdin] to enter the input you want to send to the executable. If you use -m cl, then you also need use -i [INPUT_MESSAGE] or -c [PATH_To_File] to specify the message." << std::endl; return -1; } if (cmd.input_mode == inputMode::NONE) { - std::cout << "Use -m to specify the input. Please use --help to get more information." << std::endl; + std::cout << "Please use -m to specify the input. Please use --help to get more information." << std::endl; return -1; } if(cmd.input_mode == inputMode::CL and !cmd.cmdLineFlag) { - std::cout << "Please, use -i to enter a String which you want to send to the interface. It is also possible to use -m stdin instead." << std::endl; + std::cout << "Please use -i [INPUT_MESSAGE] or -c [PATH_To_File] to enter a String which you want to send to the interface. It is also possible to use -m stdin instead." << std::endl; return -1; } iox::log::LogManager::GetLogManager().SetDefaultLogLevel(m_logLevel); FuzzHelper aFuzzHelper; std::shared_ptr aRouDi; + + if (cmd.fuzzing_API == fuzzingAPI::TOML) + { + if(!cmd.tomlFileFlag) + { + std::cout << "Please use -t [PATH_To_File] to specify a file where the messages are written to which are sent to the TOML configuration parser." << std::endl; + return -1; + } + else + { + allMessages = aFuzzHelper.combineString(allMessages); + } + } + if (cmd.fuzzing_API == fuzzingAPI::UDS or cmd.fuzzing_API == fuzzingAPI::COM) // Start RouDi { aRouDi = aFuzzHelper.startRouDiThread(); @@ -288,6 +360,7 @@ int main (int argc, char* argv[]) timeout += 1; } } + if(cmd.input_mode == inputMode::CL or cmd.input_mode == inputMode::STDIN) { Fuzzing aFuzzer; @@ -310,8 +383,8 @@ int main (int argc, char* argv[]) case fuzzingAPI::TOML: { + aFuzzer.fuzzing_TOML_parser(aMessage, cmd.tomlFile); iox::LogDebug() << "Messages sent to TOML Parser: " << aMessage; - aFuzzer.fuzzing_TOML_parser(aMessage); } break; default: @@ -323,6 +396,7 @@ int main (int argc, char* argv[]) } break; }; } + } else @@ -334,9 +408,5 @@ int main (int argc, char* argv[]) } -//@TODO README - - - diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp index f1fde787cf3..91dbe918380 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp @@ -50,6 +50,7 @@ class FuzzHelper public: std::vector getStdInMessages(); std::shared_ptr startRouDiThread(); + std::vector combineString(std::vector allMessages); bool checkIsRouDiRunning(); @@ -62,7 +63,7 @@ class Fuzzing Fuzzing(); void fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage); int fuzzingRouDiUDS(std::string aMessage); - void fuzzing_TOML_parser(std::string toml_file); + void fuzzing_TOML_parser(std::string toml_file, std::string temp_file); }; @@ -78,7 +79,9 @@ class CmdLineParserFuzzing bool errorFlag; bool cmdLineFlag; bool helpFlag; + bool tomlFileFlag; std::vector allMessages; + std::string tomlFile; }; From 079d040de4d44e53a01bef17ffbb2cab79f8f32c Mon Sep 17 00:00:00 2001 From: "Mueller Julian (CC-AD/EYF1)" Date: Fri, 12 Mar 2021 12:04:47 +0100 Subject: [PATCH 003/127] iox-558 Cosmetic changes for the help method. Signed-off-by: Mueller Julian (CC-AD/EYF1) --- iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index 1827ed16e90..35764a577e7 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -181,7 +181,7 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) { std::cout << "Usage: " << argv[0] << " [options]" << std::endl; std::cout << "Options:" << std::endl; - std::cout << "-h, --help Display help." << std::endl; + std::cout << "-h, --help Display help." << std::endl; std::cout << "-f, --fuzzing-api Specify API which will be fuzzed." << std::endl; std::cout << " {uds, com, toml}" << std::endl; std::cout << " uds: Starts RouDi and sends messages via Unix Domain Sockets. Multiple messages can be sent. (e.g.: register message first and then offer service)." << std::endl; @@ -192,8 +192,8 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) std::cout << " cl: Send input via commandline. Needs parameter i to send the input." << std::endl; std::cout << "-c, --command-line-file : Read the specified file and send the input to the interface." << std::endl; std::cout << "-i, --command-line-input : Send the input via this command line, requires to use input-mode cl. It's possible to send several commands with several -i commands." << std::endl; - std::cout << "-t, --toml-file : Needs to be used when TOML is parsed. The file is used to write messages which will be parsed by the TOML configuration parser." << std::endl; - std::cout << "-l, --log-level {off, fatal, debug} : Set the log level. Off is standard;" << std::endl; + std::cout << "-t, --toml-file : Needs to be used when TOML is parsed. The file is used to write messages which will be parsed by the TOML configuration parser." << std::endl; + std::cout << "-l, --log-level {off, fatal, debug} : Set the log level. Off is standard;" << std::endl; helpFlag = true; } break; From 82ed4fba1bb0ec1696acd1919a57538696139580 Mon Sep 17 00:00:00 2001 From: "Mueller Julian (CC-AD/EYF1)" Date: Wed, 17 Mar 2021 13:52:09 +0100 Subject: [PATCH 004/127] iox-558 Some improvements for the help text + added a delay for UDS because sometimes RouDi was already terminated before a crash was triggered Signed-off-by: Mueller Julian (CC-AD/EYF1) --- iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index 35764a577e7..b4cb96ca6c7 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -121,6 +121,7 @@ int Fuzzing::fuzzingRouDiUDS(std::string aMessage) if (connectfd != -1) { sendto(sockfd, aMessage.c_str(), aMessage.length()+1, static_cast(0), nullptr, static_cast(0)); + usleep(500000); //0.5 second We need to wait otherwise it can happen that RouDi did not process the message } else { @@ -181,7 +182,7 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) { std::cout << "Usage: " << argv[0] << " [options]" << std::endl; std::cout << "Options:" << std::endl; - std::cout << "-h, --help Display help." << std::endl; + std::cout << "-h, --help Display help." << std::endl; std::cout << "-f, --fuzzing-api Specify API which will be fuzzed." << std::endl; std::cout << " {uds, com, toml}" << std::endl; std::cout << " uds: Starts RouDi and sends messages via Unix Domain Sockets. Multiple messages can be sent. (e.g.: register message first and then offer service)." << std::endl; From 674218d1b7831241abd775ebf6c998fe833d7ef2 Mon Sep 17 00:00:00 2001 From: "Mueller Julian (CC-AD/EYF1)" Date: Thu, 25 Mar 2021 15:38:38 +0100 Subject: [PATCH 005/127] iox-558 Added Doxygen and additionally created one file per class Signed-off-by: Mueller Julian (CC-AD/EYF1) --- .../test/fuzztests/cmdlineparserfuzzing.cpp | 175 +++++++++++ .../test/fuzztests/cmdlineparserfuzzing.hpp | 60 ++++ iceoryx_posh/test/fuzztests/fuzz_helper.cpp | 65 ++++ iceoryx_posh/test/fuzztests/fuzz_helper.hpp | 48 +++ iceoryx_posh/test/fuzztests/fuzzing.cpp | 78 +++++ iceoryx_posh/test/fuzztests/fuzzing.hpp | 55 ++++ .../fuzztests/fuzztests_roudi_wrapper.cpp | 291 +----------------- .../fuzztests/fuzztests_roudi_wrapper.hpp | 82 +---- iceoryx_posh/test/fuzztests/roudi_fuzz.cpp | 31 ++ iceoryx_posh/test/fuzztests/roudi_fuzz.hpp | 34 ++ .../test/fuzztests/string_to_ipc_message.hpp | 29 ++ 11 files changed, 594 insertions(+), 354 deletions(-) create mode 100644 iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp create mode 100644 iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp create mode 100644 iceoryx_posh/test/fuzztests/fuzz_helper.cpp create mode 100644 iceoryx_posh/test/fuzztests/fuzz_helper.hpp create mode 100644 iceoryx_posh/test/fuzztests/fuzzing.cpp create mode 100644 iceoryx_posh/test/fuzztests/fuzzing.hpp create mode 100644 iceoryx_posh/test/fuzztests/roudi_fuzz.cpp create mode 100644 iceoryx_posh/test/fuzztests/roudi_fuzz.hpp create mode 100644 iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp new file mode 100644 index 00000000000..f6850236a36 --- /dev/null +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp @@ -0,0 +1,175 @@ +// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "cmdlineparserfuzzing.hpp" +#include "iceoryx_utils/platform/getopt.hpp" +#include "iceoryx_posh/internal/log/posh_logging.hpp" +#include "fuzz_helper.hpp" +#include +#include +#include + +iox::log::LogLevel m_logLevel{iox::log::LogLevel::kOff}; + +CmdLineParserFuzzing::CmdLineParserFuzzing() +{ + fuzzingAPI = fuzzingAPI::NONE; + inputMode = inputMode::NONE; + errorFlag = true; + cmdLineFlag = false; + helpFlag = false; + tomlFileFlag = false; + tomlFile = ""; + +} + +std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) noexcept +{ + constexpr option longOptions[] = {{"help", no_argument, nullptr, 'h'}, + {"fuzzing-API", required_argument, nullptr, 'f'}, + {"input-mode", required_argument, nullptr, 'm'}, + {"command-line-file", required_argument, nullptr, 'c'}, + {"command-line-input", required_argument, nullptr, 'i'}, + {"toml-file", required_argument, nullptr, 't'}, + {"log-level", required_argument, nullptr, 'l'}, + {nullptr, 0, nullptr, 0}}; + constexpr const char* shortOptions = "hf:m:i:l:c:t:"; + int32_t index; + int32_t opt{-1}; + while ((opt = getopt_long(argc, argv, shortOptions, longOptions, &index), opt != -1)) + { + errorFlag = false; + switch (opt) + { + case 'h': + { + std::cout << "Usage: " << argv[0] << " [options]" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << "-h, --help Display help." << std::endl; + std::cout << "-f, --fuzzing-api Specify API which will be fuzzed." << std::endl; + std::cout << " {uds, com, toml}" << std::endl; + std::cout << " uds: Starts RouDi and sends messages via Unix Domain Sockets. Multiple messages can be sent. (e.g.: register message first and then offer service)." << std::endl; + std::cout << " com: Invokes the processMessage method in RouDi directly. This abstracts the IPC and is faster but multiple messages are not supported." << std::endl; + std::cout << " toml: Send inputs to test the TOML config file parser. A file is created in your current working directory and the path is sent to the Parser." << std::endl; + std::cout << "-m, --input-mode {stdin, cl}" << std::endl; + std::cout << " stdin: Send input via stdin." << std::endl; + std::cout << " cl: Send input via commandline. Needs parameter i to send the input." << std::endl; + std::cout << "-c, --command-line-file : Read the specified file and send the input to the interface." << std::endl; + std::cout << "-i, --command-line-input : Send the input via this command line, requires to use input-mode cl. It's possible to send several commands with several -i commands." << std::endl; + std::cout << "-t, --toml-file : Needs to be used when TOML is parsed. The file is used to write messages which will be parsed by the TOML configuration parser." << std::endl; + std::cout << "-l, --log-level {off, fatal, debug} : Set the log level. Off is standard;" << std::endl; + helpFlag = true; + } break; + + case 'f': + { + if (strcmp(optarg, "uds") == 0) + { + fuzzingAPI = fuzzingAPI::UDS; + } + else if (strcmp(optarg, "com") == 0) + { + fuzzingAPI = fuzzingAPI::COM; + } + else if (strcmp(optarg, "toml") == 0) + { + fuzzingAPI = fuzzingAPI::TOML; + } + else + { + std::cout << "Options for fuzzing-api are 'uds', 'com' and 'toml'!" << std::endl; + } + } break; + + case 'm': + { + if (strcmp(optarg, "stdin") == 0) + { + inputMode = inputMode::STDIN; + FuzzHelper aFuzzHelper; + allMessages = aFuzzHelper.getStdInMessages(); + } + else if (strcmp(optarg, "cl") == 0) + { + inputMode = inputMode::CL; + } + else + { + std::cout << "Options for input-mode are 'stdin' and 'cl'!" << std::endl; + } + } break; + case 'i': + { + cmdLineFlag = true; + allMessages.emplace_back(optarg); + + } break; + + case 'c': + { + cmdLineFlag = true; + std::ifstream ifile; + ifile.open(optarg); + if(ifile) + { + std::string tempFileContent( (std::istreambuf_iterator(ifile) ), (std::istreambuf_iterator())); + allMessages.emplace_back(tempFileContent); + } + + else + { + std::cout<<"Error cannot open file. Either file does not exist or I don't have the permissions to open it."; + errorFlag = true; + return allMessages; + } + }break; + + case 'l': + { + if (strcmp(optarg, "off") == 0) + { + m_logLevel = iox::log::LogLevel::kOff; + } + else if (strcmp(optarg, "fatal") == 0) + { + m_logLevel = iox::log::LogLevel::kFatal; + } + else if (strcmp(optarg, "debug") == 0) + { + m_logLevel = iox::log::LogLevel::kDebug; + } + else + { + std::cout << "Options for Logging are 'off', 'fatal' and 'debug'!" << std::endl; + } + iox::log::LogManager::GetLogManager().SetDefaultLogLevel(m_logLevel); + } break; + case 't': + { + tomlFileFlag = true; + tomlFile = optarg; + } break; + default: + { + std::cout << "Unknown command.\n" << std::endl; + errorFlag = true; + return allMessages; + } break; + }; + } + return allMessages; +} diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp new file mode 100644 index 00000000000..06262c9cc63 --- /dev/null +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp @@ -0,0 +1,60 @@ +// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + + +enum class fuzzingAPI +{ + NONE, + UDS, + COM, + TOML +}; + +enum class inputMode +{ + NONE, + STDIN, + CL +}; + +/// @brief CmdLineParserFuzzing is a class which parses the command lines to configure the Fuzz Wrappers for example to tell +/// which interface shall be fuzzed. +class CmdLineParserFuzzing +{ + public: + CmdLineParserFuzzing(); + /// @brief Parses the command line parameters whiich are entered by starting the fuzz wrappers + /// @param[in] amount of arguments given to the method + /// @param[in] containing the command line parameters + /// @param[out] Containing the messages which shall be sent to the interface + std::vector parseCmd(int argc, char* argv[]) noexcept; + + bool helpFlag; + inputMode inputMode; + bool errorFlag; + bool cmdLineFlag; + fuzzingAPI fuzzingAPI; + bool tomlFileFlag; + std::string tomlFile; + + private: + + std::vector allMessages; + }; diff --git a/iceoryx_posh/test/fuzztests/fuzz_helper.cpp b/iceoryx_posh/test/fuzztests/fuzz_helper.cpp new file mode 100644 index 00000000000..d3cf3d9749a --- /dev/null +++ b/iceoryx_posh/test/fuzztests/fuzz_helper.cpp @@ -0,0 +1,65 @@ +// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "fuzz_helper.hpp" +#include "fuzzing.hpp" +#include "iceoryx_posh/roudi/iceoryx_roudi_components.hpp" + + +std::vector FuzzHelper::getStdInMessages() +{ + std::vector stdInMessages; + for (std::string line; std::getline(std::cin, line);) + { + stdInMessages.push_back(line); + } + return stdInMessages; +} + +std::shared_ptr FuzzHelper::startRouDiThread() +{ + static iox::roudi::IceOryxRouDiComponents m_rouDiComponents(iox::RouDiConfig_t().setDefaults()); + static iox::RouDiConfig_t m_config = iox::RouDiConfig_t().setDefaults(); + std::shared_ptr aRouDi(new RouDiFuzz(m_rouDiComponents.m_rouDiMemoryManager, m_rouDiComponents.m_portManager)); + return aRouDi; +} + +bool FuzzHelper::checkIsRouDiRunning() +{ + Fuzzing aFuzzer; + int udsStatus = aFuzzer.fuzzingRouDiUDS("Hello Roudi!"); + if (udsStatus == -1) + { + return false; + } + else + { + return true; + } +} + +std::vector FuzzHelper::combineString(std::vector allMessages) +{ + std::string tempString = ""; + for(std::string aMessage: allMessages) + { + tempString += aMessage + "\n"; + } + allMessages.clear(); + allMessages.emplace_back(tempString); + return allMessages; +} diff --git a/iceoryx_posh/test/fuzztests/fuzz_helper.hpp b/iceoryx_posh/test/fuzztests/fuzz_helper.hpp new file mode 100644 index 00000000000..e4256552b8e --- /dev/null +++ b/iceoryx_posh/test/fuzztests/fuzz_helper.hpp @@ -0,0 +1,48 @@ +// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include "roudi_fuzz.hpp" +/// @brief Class to implement some help methods for the fuzz wrapper +class FuzzHelper +{ + + public: + + /// @brief Reads messages from stdin and writes them into a std::vector + /// @param[in] message via stdin + /// @param[out] std::vector containing std::strings of the messages from stdin. Each std::string in the vector is one line of stdin. + /// this means that if there is one newline in stdin, there will be two std::strings, with two newlines, there will be three + /// meessages,... + std::vector getStdInMessages(); + + /// @brief a shared Ptr to a RouDi thread which will be used to keep the thread alive until the message is processed by RouDi + /// @param[out] a shated_ptr to RouDiFuzz which inherits from RouDi + std::shared_ptr startRouDiThread(); + + /// @brief Splitted messages in allMessages are put together as one String. This is used for TOML parser for example because one message + /// can contain newlines + /// @param[in] std::vector containing several std::string messages which shall be sent to an interface + /// @param[out] std::vector containing one std::string message + std::vector combineString(std::vector allMessages); + + /// @brief A method to check if RouDi is alive. It checks if the UDS is available and then sends a default message to RouDi + /// @param[out] Boolean value indicating if RouDi is available + bool checkIsRouDiRunning(); + + +}; diff --git a/iceoryx_posh/test/fuzztests/fuzzing.cpp b/iceoryx_posh/test/fuzztests/fuzzing.cpp new file mode 100644 index 00000000000..15104284164 --- /dev/null +++ b/iceoryx_posh/test/fuzztests/fuzzing.cpp @@ -0,0 +1,78 @@ +// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "fuzzing.hpp" +#include +#include "cpptoml.h" +#include + +std::string const UDS_NAME = "/tmp/"; + +Fuzzing::Fuzzing() +{ + +} + +void Fuzzing::fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage) +{ + if(aRouDi != nullptr) + { + aRouDi->processMessageFuzz(aMessage); + } + else + { + iox::LogDebug() << "Error, the Smart Pointer for RouDI which is used to call the method 'processMessage' is NULL"; + } +} + +int Fuzzing::fuzzingRouDiUDS(std::string aMessage) +{ + int sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); + struct sockaddr aSockAddr; + aSockAddr.sa_family = AF_LOCAL; + std::string roudiName = UDS_NAME + iox::roudi::IPC_CHANNEL_ROUDI_NAME; + strncpy(aSockAddr.sa_data, roudiName.c_str(), sizeof(aSockAddr.sa_data)); + int connectfd = connect(sockfd, &aSockAddr, sizeof(aSockAddr)); + if (connectfd != -1) + { + sendto(sockfd, aMessage.c_str(), aMessage.length()+1, static_cast(0), nullptr, static_cast(0)); + usleep(500000); //0.5 second We need to wait otherwise it can happen that RouDi did not process the message + } + else + { + iox::LogDebug() << "Could not connect to RoudI"; + } + return connectfd; +} + + +void Fuzzing::fuzzingTOMLParser(std::string aMessage, std::string tempFile) +{ + std::ofstream aTomlFile; + aTomlFile.open(tempFile); + std::cout << "Sent to TOML: " << aMessage << std::endl; + if(aTomlFile.is_open()) + { + aTomlFile << aMessage; + cpptoml::parse_file(tempFile); + aTomlFile.close(); + } + else + { + iox::LogDebug() << "Cannot open file to send it to TOML Parser: " << tempFile; + } +} diff --git a/iceoryx_posh/test/fuzztests/fuzzing.hpp b/iceoryx_posh/test/fuzztests/fuzzing.hpp new file mode 100644 index 00000000000..7637181815c --- /dev/null +++ b/iceoryx_posh/test/fuzztests/fuzzing.hpp @@ -0,0 +1,55 @@ +// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + + + + + +#ifndef FUZZING_HPP +#define FUZZING_HPP + +#include "roudi_fuzz.hpp" +#include +#include + +/// @brief Fuzzing is a class which is used to send a message to different interfaces which shall be fuzzed. +class Fuzzing +{ + public: + Fuzzing(); + + /// @brief fuzzingRouDiCom is a method to test the processMessage method of RouDI. It shall be independent from the underlying protocol + /// such as uds. It should also be slightly faster since some functions are not invoked compared to uds fuzzing. + /// However, a RouDi thread is also started with this approach because otherwise it was not be possible to invoke + /// the processMessage method within RouDi without directly modifying the code in RouDi. + /// @param [in] shared_ptr to RouDiFuzz such that processMessage can be invoked + /// @param [in] std::string containing a message which shall be sent to the interface + void fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage); + + /// @brief fuzzingRouDiUDS is a method to test the Unix Domain Socket interface of RouDI. It connects to RouDi's uds and sends the message + /// given as input to RouDi + /// @param [in] std::string containing a message which shall be sent to the interface + /// @param [out] int as result of connect(). If the int = -1 it means that it was not possible to connect to the socket + int fuzzingRouDiUDS(std::string aMessage); + + /// @brief fuzzingTOMLParser is a method to send a message to the TOML parser. + /// @param [in] std::string containing a message which shall be sent to the interface + /// @param [in] std::string containing a valid path to an empty file which will be used to write the tomlFile into. This is necessary + /// because the TOML parser expects a path to a file as input + void fuzzingTOMLParser(std::string tomlFile, std::string tempFile); +}; +#endif /* FUZZING_HPP */ diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index b4cb96ca6c7..9512ec7dd92 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -16,286 +16,16 @@ #include "iceoryx_posh/internal/roudi/roudi.hpp" #include "iceoryx_posh/internal/log/posh_logging.hpp" #include "iceoryx_posh/roudi/iceoryx_roudi_components.hpp" -#include "iceoryx_utils/platform/getopt.hpp" #include "fuzztests_roudi_wrapper.hpp" -#include "cpptoml.h" +#include "fuzzing.hpp" +#include "fuzz_helper.hpp" +#include "cmdlineparserfuzzing.hpp" #include #include -std::string const UDS_NAME = "/tmp/"; unsigned const char TIMEOUT = 50; //5s for a Timeout #define INTERFACE_NAME "/test" - -iox::log::LogLevel m_logLevel{iox::log::LogLevel::kOff}; - - -StringToIPCMessage::StringToIPCMessage(const iox::ProcessName_t& name, const int64_t maxMessages, const int64_t messageSize) : iox::runtime::IpcInterfaceBase(name, maxMessages, messageSize) -{ -} - -RouDiFuzz::RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, iox::roudi::PortManager& portManager, iox::roudi::RouDi::RoudiStartupParameters aStartupParameter = {iox::roudi::MonitoringMode::OFF, false, iox::roudi::RouDi::RuntimeMessagesThreadStart::IMMEDIATE, iox::version::CompatibilityCheckLevel::OFF}) : iox::roudi::RouDi(roudiMemoryInterface, portManager, aStartupParameter) -{ -} - -void RouDiFuzz::processMessageFuzz(std::string aMessage) -{ - iox::runtime::IpcMessage ipcMessage; - StringToIPCMessage::setMessageFromString(aMessage.c_str(), ipcMessage); - iox::runtime::IpcMessageType cmd = iox::runtime::stringToIpcMessageType(ipcMessage.getElementAtIndex(0).c_str()); - std::string processName = ipcMessage.getElementAtIndex(1); - iox::roudi::RouDi::processMessage(ipcMessage, cmd, iox::ProcessName_t(iox::cxx::TruncateToCapacity, processName)); -} - - -std::vector FuzzHelper::getStdInMessages() -{ - std::vector stdInMessages; - for (std::string line; std::getline(std::cin, line);) - { - stdInMessages.push_back(line); - } - return stdInMessages; -} - -std::shared_ptr FuzzHelper::startRouDiThread() -{ - static iox::roudi::IceOryxRouDiComponents m_rouDiComponents(iox::RouDiConfig_t().setDefaults()); - static iox::RouDiConfig_t m_config = iox::RouDiConfig_t().setDefaults(); - std::shared_ptr aRouDi(new RouDiFuzz(m_rouDiComponents.m_rouDiMemoryManager, m_rouDiComponents.m_portManager)); - return aRouDi; -} - -bool FuzzHelper::checkIsRouDiRunning() -{ - Fuzzing aFuzzer; - int udsStatus = aFuzzer.fuzzingRouDiUDS("Hello Roudi!"); - if (udsStatus == -1) - { - return false; - } - else - { - return true; - } -} - -std::vector FuzzHelper::combineString(std::vector allMessages) -{ - std::string tempString = ""; - for(std::string aMessage: allMessages) - { - tempString += aMessage + "\n"; - } - allMessages.clear(); - allMessages.emplace_back(tempString); - return allMessages; -} - - -Fuzzing::Fuzzing() -{ - -} - -void Fuzzing::fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage) -{ - if(aRouDi != nullptr) - { - aRouDi->processMessageFuzz(aMessage); - } - else - { - iox::LogDebug() << "Error, the Smart Pointer for RouDI which is used to call the method 'processMessage' is NULL"; - } -} - -int Fuzzing::fuzzingRouDiUDS(std::string aMessage) -{ - int sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); - struct sockaddr aSockAddr; - aSockAddr.sa_family = AF_LOCAL; - std::string roudiName = UDS_NAME + iox::roudi::IPC_CHANNEL_ROUDI_NAME; - strncpy(aSockAddr.sa_data, roudiName.c_str(), sizeof(aSockAddr.sa_data)); - int connectfd = connect(sockfd, &aSockAddr, sizeof(aSockAddr)); - if (connectfd != -1) - { - sendto(sockfd, aMessage.c_str(), aMessage.length()+1, static_cast(0), nullptr, static_cast(0)); - usleep(500000); //0.5 second We need to wait otherwise it can happen that RouDi did not process the message - } - else - { - iox::LogDebug() << "Could not connect to RoudI"; - } - return connectfd; -} - - -void Fuzzing::fuzzing_TOML_parser(std::string aMessage, std::string tempFile) -{ - std::ofstream aTomlFile; - aTomlFile.open(tempFile); - std::cout << "Sent to TOML: " << aMessage << std::endl; - if(aTomlFile.is_open()) - { - aTomlFile << aMessage; - cpptoml::parse_file(tempFile); - aTomlFile.close(); - } - else - { - iox::LogDebug() << "Cannot open file to send it to TOML Parser: " << tempFile; - } -} - -CmdLineParserFuzzing::CmdLineParserFuzzing() -{ - fuzzing_API = fuzzingAPI::NONE; - input_mode = inputMode::NONE; - errorFlag = true; - cmdLineFlag = false; - helpFlag = false; - tomlFileFlag = false; - tomlFile = ""; - -} - -std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) noexcept -{ - constexpr option longOptions[] = {{"help", no_argument, nullptr, 'h'}, - {"fuzzing-API", required_argument, nullptr, 'f'}, - {"input-mode", required_argument, nullptr, 'm'}, - {"command-line-file", required_argument, nullptr, 'c'}, - {"command-line-input", required_argument, nullptr, 'i'}, - {"toml-file", required_argument, nullptr, 't'}, - {"log-level", required_argument, nullptr, 'l'}, - {nullptr, 0, nullptr, 0}}; - constexpr const char* shortOptions = "hf:m:i:l:c:t:"; - int32_t index; - int32_t opt{-1}; - while ((opt = getopt_long(argc, argv, shortOptions, longOptions, &index), opt != -1)) - { - errorFlag = false; - switch (opt) - { - case 'h': - { - std::cout << "Usage: " << argv[0] << " [options]" << std::endl; - std::cout << "Options:" << std::endl; - std::cout << "-h, --help Display help." << std::endl; - std::cout << "-f, --fuzzing-api Specify API which will be fuzzed." << std::endl; - std::cout << " {uds, com, toml}" << std::endl; - std::cout << " uds: Starts RouDi and sends messages via Unix Domain Sockets. Multiple messages can be sent. (e.g.: register message first and then offer service)." << std::endl; - std::cout << " com: Invokes the processMessage method in RouDi directly. This abstracts the IPC and is faster but multiple messages are not supported." << std::endl; - std::cout << " toml: Send inputs to test the TOML config file parser. A file is created in your current working directory and the path is sent to the Parser." << std::endl; - std::cout << "-m, --input-mode {stdin, cl}" << std::endl; - std::cout << " stdin: Send input via stdin." << std::endl; - std::cout << " cl: Send input via commandline. Needs parameter i to send the input." << std::endl; - std::cout << "-c, --command-line-file : Read the specified file and send the input to the interface." << std::endl; - std::cout << "-i, --command-line-input : Send the input via this command line, requires to use input-mode cl. It's possible to send several commands with several -i commands." << std::endl; - std::cout << "-t, --toml-file : Needs to be used when TOML is parsed. The file is used to write messages which will be parsed by the TOML configuration parser." << std::endl; - std::cout << "-l, --log-level {off, fatal, debug} : Set the log level. Off is standard;" << std::endl; - helpFlag = true; - } break; - - case 'f': - { - if (strcmp(optarg, "uds") == 0) - { - fuzzing_API = fuzzingAPI::UDS; - } - else if (strcmp(optarg, "com") == 0) - { - fuzzing_API = fuzzingAPI::COM; - } - else if (strcmp(optarg, "toml") == 0) - { - fuzzing_API = fuzzingAPI::TOML; - } - else - { - std::cout << "Options for fuzzing-api are 'uds', 'com' and 'toml'!" << std::endl; - } - } break; - - case 'm': - { - if (strcmp(optarg, "stdin") == 0) - { - input_mode = inputMode::STDIN; - FuzzHelper aFuzzHelper; - allMessages = aFuzzHelper.getStdInMessages(); - } - else if (strcmp(optarg, "cl") == 0) - { - input_mode = inputMode::CL; - } - else - { - std::cout << "Options for input-mode are 'stdin' and 'cl'!" << std::endl; - } - } break; - case 'i': - { - cmdLineFlag = true; - allMessages.emplace_back(optarg); - - } break; - - case 'c': - { - cmdLineFlag = true; - std::ifstream ifile; - ifile.open(optarg); - if(ifile) - { - std::string tempFileContent( (std::istreambuf_iterator(ifile) ), (std::istreambuf_iterator())); - allMessages.emplace_back(tempFileContent); - } - - else - { - std::cout<<"Error cannot open file. Either file does not exist or I don't have the permissions to open it."; - errorFlag = true; - return allMessages; - } - }break; - - case 'l': - { - if (strcmp(optarg, "off") == 0) - { - m_logLevel = iox::log::LogLevel::kOff; - } - else if (strcmp(optarg, "fatal") == 0) - { - m_logLevel = iox::log::LogLevel::kFatal; - } - else if (strcmp(optarg, "debug") == 0) - { - m_logLevel = iox::log::LogLevel::kDebug; - } - else - { - std::cout << "Options for Logging are 'off', 'fatal' and 'debug'!" << std::endl; - } - } break; - case 't': - { - tomlFileFlag = true; - tomlFile = optarg; - } break; - default: - { - std::cout << "Unknown command.\n" << std::endl; - errorFlag = true; - return allMessages; - } break; - }; - } - return allMessages; -} - int main (int argc, char* argv[]) { CmdLineParserFuzzing cmd; @@ -318,22 +48,21 @@ int main (int argc, char* argv[]) return -1; } - if (cmd.input_mode == inputMode::NONE) + if (cmd.inputMode == inputMode::NONE) { std::cout << "Please use -m to specify the input. Please use --help to get more information." << std::endl; return -1; } - if(cmd.input_mode == inputMode::CL and !cmd.cmdLineFlag) + if(cmd.inputMode == inputMode::CL and !cmd.cmdLineFlag) { std::cout << "Please use -i [INPUT_MESSAGE] or -c [PATH_To_File] to enter a String which you want to send to the interface. It is also possible to use -m stdin instead." << std::endl; return -1; } - iox::log::LogManager::GetLogManager().SetDefaultLogLevel(m_logLevel); FuzzHelper aFuzzHelper; std::shared_ptr aRouDi; - if (cmd.fuzzing_API == fuzzingAPI::TOML) + if (cmd.fuzzingAPI == fuzzingAPI::TOML) { if(!cmd.tomlFileFlag) { @@ -346,7 +75,7 @@ int main (int argc, char* argv[]) } } - if (cmd.fuzzing_API == fuzzingAPI::UDS or cmd.fuzzing_API == fuzzingAPI::COM) // Start RouDi + if (cmd.fuzzingAPI == fuzzingAPI::UDS or cmd.fuzzingAPI == fuzzingAPI::COM) // Start RouDi { aRouDi = aFuzzHelper.startRouDiThread(); unsigned char timeout = 0; @@ -362,13 +91,13 @@ int main (int argc, char* argv[]) } } - if(cmd.input_mode == inputMode::CL or cmd.input_mode == inputMode::STDIN) + if(cmd.inputMode == inputMode::CL or cmd.inputMode == inputMode::STDIN) { Fuzzing aFuzzer; for(std::string aMessage: allMessages) { - switch (cmd.fuzzing_API) + switch (cmd.fuzzingAPI) { case fuzzingAPI::COM: { @@ -384,7 +113,7 @@ int main (int argc, char* argv[]) case fuzzingAPI::TOML: { - aFuzzer.fuzzing_TOML_parser(aMessage, cmd.tomlFile); + aFuzzer.fuzzingTOMLParser(aMessage, cmd.tomlFile); iox::LogDebug() << "Messages sent to TOML Parser: " << aMessage; } break; diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp index 91dbe918380..4ce06688d8d 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp @@ -1,4 +1,5 @@ -// Copyright (c) 2020 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,80 +12,15 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +/// @brief fuzztests_roudi_wrapper contains the main method for the RouDi Wrappers which can be used to fuzz several interfaces -enum class fuzzingAPI -{ - NONE, - UDS, - COM, - TOML -}; - -enum class inputMode -{ - NONE, - STDIN, - CL -}; - - -class StringToIPCMessage : public iox::runtime::IpcInterfaceBase -{ - public: - StringToIPCMessage(const iox::ProcessName_t& name, const int64_t maxMessages, const int64_t messageSize); - using iox::runtime::IpcInterfaceBase::setMessageFromString; -}; - - -class RouDiFuzz : iox::roudi::RouDi -{ - public: - RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, iox::roudi::PortManager& portManager, iox::roudi::RouDi::RoudiStartupParameters aStartupParameter); - void processMessageFuzz( std::string aMessage); -}; - -class FuzzHelper -{ - - public: - std::vector getStdInMessages(); - std::shared_ptr startRouDiThread(); - std::vector combineString(std::vector allMessages); - bool checkIsRouDiRunning(); - - -}; - -class Fuzzing -{ - - public: - Fuzzing(); - void fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage); - int fuzzingRouDiUDS(std::string aMessage); - void fuzzing_TOML_parser(std::string toml_file, std::string temp_file); -}; - - -class CmdLineParserFuzzing -{ - public: - - CmdLineParserFuzzing(); - std::vector parseCmd(int argc, char* argv[]) noexcept; - - fuzzingAPI fuzzing_API; - inputMode input_mode; - bool errorFlag; - bool cmdLineFlag; - bool helpFlag; - bool tomlFileFlag; - std::vector allMessages; - std::string tomlFile; - }; - - +/// @brief Main function of the Fuzz Wrapper +/// @param[in] amount of arguments given to the method +/// @param[in] containing the command line parameters +/// @param[out] int containing the status if the fuzzing was successfull. If the return value is -1 something went wrong. int main (int argc, char* argv[]); diff --git a/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp b/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp new file mode 100644 index 00000000000..24f777d5fa3 --- /dev/null +++ b/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "roudi_fuzz.hpp" +#include "string_to_ipc_message.hpp" +RouDiFuzz::RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, iox::roudi::PortManager& portManager, iox::roudi::RouDi::RoudiStartupParameters aStartupParameter) : iox::roudi::RouDi(roudiMemoryInterface, portManager, aStartupParameter) +{ +} + +void RouDiFuzz::processMessageFuzz(std::string aMessage) +{ + iox::runtime::IpcMessage ipcMessage; + StringToIPCMessage::setMessageFromString(aMessage.c_str(), ipcMessage); + iox::runtime::IpcMessageType cmd = iox::runtime::stringToIpcMessageType(ipcMessage.getElementAtIndex(0).c_str()); + std::string processName = ipcMessage.getElementAtIndex(1); + iox::roudi::RouDi::processMessage(ipcMessage, cmd, iox::ProcessName_t(iox::cxx::TruncateToCapacity, processName)); +} diff --git a/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp b/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp new file mode 100644 index 00000000000..661158d61b5 --- /dev/null +++ b/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp @@ -0,0 +1,34 @@ +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef ROUDI_FUZZ_HPP +#define ROUDI_FUZZ_HPP + +#include "iceoryx_posh/internal/roudi/roudi.hpp" + +/// @brief RouDiFuzz is a class which inherits from iox::roudi::RouDi to make some protected methods avaialble for Fuzzing. This is necessary, to directly injects messages +/// in these messages to test the robustness of the interfaces + +class RouDiFuzz : iox::roudi::RouDi +{ + public: + RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, iox::roudi::PortManager& portManager, iox::roudi::RouDi::RoudiStartupParameters = {iox::roudi::MonitoringMode::OFF, false, iox::roudi::RouDi::RuntimeMessagesThreadStart::IMMEDIATE, iox::version::CompatibilityCheckLevel::OFF}); + + /// @brief [in] Send a message to the processMessage method of RouDi + /// @param [in] Message which should be sent to the processMessage method of RouDi + void processMessageFuzz(std::string aMessage); +}; +#endif //ROUDI_FUZZ_HPP diff --git a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp new file mode 100644 index 00000000000..e4948c37986 --- /dev/null +++ b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +/// @brief The StringToIPCMessage is a class which inherits from iox::runtime::IpcInterfaceBase to make the protected method +/// iox::runtime::IpcInterfaceBase::setMessageFromString public and accessible for the fuzz test. +class StringToIPCMessage : public iox::runtime::IpcInterfaceBase +{ + public: + StringToIPCMessage(const iox::ProcessName_t& name, const int64_t maxMessages, const int64_t messageSize); + + /// @brief Set the content of answer from buffer. + /// @param[in] buffer Raw message as char pointer + /// @param[out] answer Raw message is setting this IpcMessage + /// @return answer.isValid() + using iox::runtime::IpcInterfaceBase::setMessageFromString; +}; \ No newline at end of file From dc78c7c1f11d1df0f56874edc6606bf8f2692b5f Mon Sep 17 00:00:00 2001 From: "Mueller Julian (CC-AD/EYF1)" Date: Wed, 31 Mar 2021 10:59:35 +0200 Subject: [PATCH 006/127] iox-558 Changed Copyright text in all files. Correcting some typos. Adding some getters to CmdLineParserFuzzing to make the members private. Add include guards in all header. Using std::this_thread::sleep_for isntead of usleep. Improving code readability by using clang-format and clang-tidy. Signed-off-by: Mueller Julian (CC-AD/EYF1) --- iceoryx_posh/test/fuzztests/README.md | 6 +- .../test/fuzztests/cmdlineparserfuzzing.cpp | 347 +++++++++++------- .../test/fuzztests/cmdlineparserfuzzing.hpp | 88 +++-- iceoryx_posh/test/fuzztests/fuzz_helper.cpp | 60 +-- iceoryx_posh/test/fuzztests/fuzz_helper.hpp | 48 ++- iceoryx_posh/test/fuzztests/fuzzing.cpp | 6 +- iceoryx_posh/test/fuzztests/fuzzing.hpp | 58 +-- .../fuzztests/fuzztests_roudi_wrapper.cpp | 244 ++++++------ .../fuzztests/fuzztests_roudi_wrapper.hpp | 7 +- iceoryx_posh/test/fuzztests/roudi_fuzz.cpp | 18 +- iceoryx_posh/test/fuzztests/roudi_fuzz.hpp | 28 +- .../test/fuzztests/string_to_ipc_message.hpp | 30 +- 12 files changed, 529 insertions(+), 411 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/README.md b/iceoryx_posh/test/fuzztests/README.md index 0c5f8e5dab9..25379625ac0 100644 --- a/iceoryx_posh/test/fuzztests/README.md +++ b/iceoryx_posh/test/fuzztests/README.md @@ -28,9 +28,9 @@ The fuzz-wrapper binary can be found in \[path\_to\_iceoryx\]/iceoryx\_eclipse/b To start fuzzing with afl, you need two folders: One which contains examples of valid messages which can be send to the interface and one folder where the fuzzing results are stored. There is already an example folder with a valid text message to test the uds communication. It can be found within \[path\_to\_iceoryx\]/iceoryx\_posh/test/fuzztests/fuzz\_input/ . The fuzz wrappers currently support three different interfaces: the unix domain socket communication, the message parsing method and the toml configuration file parser. -1. The unix domain socket can either be tested by starting RouDi and sending messages to RouDi via uds or by directly invoking the message parsing function within RouDi. The first solution has the adventage, that the messages from uds to RouDi take the same path as normal messages from applications would do. The drawback however is that RouDi needs to be started with all the overhead which would not be necessary for this test which therefore slows the test a little bit. +1. The unix domain socket can either be tested by starting RouDi and sending messages to RouDi via uds or by directly invoking the message parsing function within RouDi. The first solution has the advantage, that the messages from uds to RouDi take the same path as normal messages from applications would do. The drawback however is that RouDi needs to be started with all the overhead which would not be necessary for this test which therefore slows the test a little bit. -2. By invoking the message parsing function directly, the method is independent from the underlying protocol such as uds. It should also be slightly faster since some functions are not invoked compared to uds fuzzing. However, a RouDi thread is also started with this approach because otherwise it was not be possible to invoke the message process function within RouDi without directly modifying the code in RouDi. +2. By invoking the message parsing function directly, the method is independent from the underlying protocol such as uds. It should also be slightly faster since some functions are not invoked compared to uds fuzzing. However, a RouDi thread is also started with this approach because otherwise it wouldn't be possible to invoke the message process function within RouDi without directly modifying the code in RouDi. 3. As a third use-case, the toml configuration parser can be tested. An example of a toml file can be found here: \[path\_to\_iceoryx\]/iceoryx\_posh/etc/iceoryx/roudi\_config\_example.toml. If you chose this method, you also need to set -t or --toml-file . A file needs to be specified which can be used to write in the messages which will send to the toml configuration parser. @@ -38,7 +38,7 @@ The interface you want to fuzz can be chosen with -f or --fuzzing-api + entering The way how the fuzzer sends messages to the interface can also be specified via -m or --input-mode 1. The messages can be sent via stdin (-m stdin) -2. The messages can be sent via using the command line (-m cl) followed by -i or --comand-line-input with the message following as argument or -c --command-line-file to specify a file where the message you want to send is located. By using -c or --command-line-file, the complete file is sent as one message and is not sent line by line. +2. The messages can be sent via using the command line (-m cl) followed by -i or --command-line-input with the message following as argument or -c --command-line-file to specify a file where the message you want to send is located. By using -c or --command-line-file, the complete file is sent as one message and is not sent line by line. Additionally, the log level can be chosen via -l or --log-level such that messages are printed for debug or not. The default is to switch logging off. 3 arguments are possible: 1. off: to disable logging (standard case) diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp index f6850236a36..fd77b55001f 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp @@ -1,5 +1,4 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,160 +15,226 @@ // SPDX-License-Identifier: Apache-2.0 #include "cmdlineparserfuzzing.hpp" -#include "iceoryx_utils/platform/getopt.hpp" -#include "iceoryx_posh/internal/log/posh_logging.hpp" #include "fuzz_helper.hpp" +#include "iceoryx_posh/internal/log/posh_logging.hpp" +#include "iceoryx_utils/platform/getopt.hpp" +#include #include #include -#include iox::log::LogLevel m_logLevel{iox::log::LogLevel::kOff}; - CmdLineParserFuzzing::CmdLineParserFuzzing() { - fuzzingAPI = fuzzingAPI::NONE; - inputMode = inputMode::NONE; - errorFlag = true; - cmdLineFlag = false; - helpFlag = false; - tomlFileFlag = false; - tomlFile = ""; + m_fuzzingAPI = FuzzingApi::NONE; + m_inputMode = InputMode::NONE; + m_errorFlag = true; + m_cmdLineFlag = false; + m_helpFlag = false; + m_tomlFileFlag = false; + m_tomlFile = ""; +} +bool CmdLineParserFuzzing::getHelpFlag() +{ + return m_helpFlag; } -std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) noexcept +InputMode CmdLineParserFuzzing::getInputMode() +{ + return m_inputMode; +} + +bool CmdLineParserFuzzing::getErrorFlag() +{ + return m_errorFlag; +} + +bool CmdLineParserFuzzing::getCmdLineFlag() +{ + return m_cmdLineFlag; +} + +FuzzingApi CmdLineParserFuzzing::getFuzzingAPI() +{ + return m_fuzzingAPI; +} + +bool CmdLineParserFuzzing::getTomlFileFlag() { - constexpr option longOptions[] = {{"help", no_argument, nullptr, 'h'}, - {"fuzzing-API", required_argument, nullptr, 'f'}, - {"input-mode", required_argument, nullptr, 'm'}, - {"command-line-file", required_argument, nullptr, 'c'}, - {"command-line-input", required_argument, nullptr, 'i'}, - {"toml-file", required_argument, nullptr, 't'}, - {"log-level", required_argument, nullptr, 'l'}, - {nullptr, 0, nullptr, 0}}; - constexpr const char* shortOptions = "hf:m:i:l:c:t:"; - int32_t index; - int32_t opt{-1}; - while ((opt = getopt_long(argc, argv, shortOptions, longOptions, &index), opt != -1)) - { - errorFlag = false; - switch (opt) - { - case 'h': - { - std::cout << "Usage: " << argv[0] << " [options]" << std::endl; - std::cout << "Options:" << std::endl; - std::cout << "-h, --help Display help." << std::endl; - std::cout << "-f, --fuzzing-api Specify API which will be fuzzed." << std::endl; - std::cout << " {uds, com, toml}" << std::endl; - std::cout << " uds: Starts RouDi and sends messages via Unix Domain Sockets. Multiple messages can be sent. (e.g.: register message first and then offer service)." << std::endl; - std::cout << " com: Invokes the processMessage method in RouDi directly. This abstracts the IPC and is faster but multiple messages are not supported." << std::endl; - std::cout << " toml: Send inputs to test the TOML config file parser. A file is created in your current working directory and the path is sent to the Parser." << std::endl; - std::cout << "-m, --input-mode {stdin, cl}" << std::endl; - std::cout << " stdin: Send input via stdin." << std::endl; - std::cout << " cl: Send input via commandline. Needs parameter i to send the input." << std::endl; - std::cout << "-c, --command-line-file : Read the specified file and send the input to the interface." << std::endl; - std::cout << "-i, --command-line-input : Send the input via this command line, requires to use input-mode cl. It's possible to send several commands with several -i commands." << std::endl; - std::cout << "-t, --toml-file : Needs to be used when TOML is parsed. The file is used to write messages which will be parsed by the TOML configuration parser." << std::endl; - std::cout << "-l, --log-level {off, fatal, debug} : Set the log level. Off is standard;" << std::endl; - helpFlag = true; - } break; + return m_tomlFileFlag; +} - case 'f': - { - if (strcmp(optarg, "uds") == 0) - { - fuzzingAPI = fuzzingAPI::UDS; - } - else if (strcmp(optarg, "com") == 0) - { - fuzzingAPI = fuzzingAPI::COM; - } - else if (strcmp(optarg, "toml") == 0) - { - fuzzingAPI = fuzzingAPI::TOML; - } - else - { - std::cout << "Options for fuzzing-api are 'uds', 'com' and 'toml'!" << std::endl; - } - } break; +std::string CmdLineParserFuzzing::getTomlFile() +{ + return m_tomlFile; +} + +std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) noexcept +{ + constexpr option LONG_OPTIONS[] = {{"help", no_argument, nullptr, 'h'}, + {"fuzzing-API", required_argument, nullptr, 'f'}, + {"input-mode", required_argument, nullptr, 'm'}, + {"command-line-file", required_argument, nullptr, 'c'}, + {"command-line-input", required_argument, nullptr, 'i'}, + {"toml-file", required_argument, nullptr, 't'}, + {"log-level", required_argument, nullptr, 'l'}, + {nullptr, 0, nullptr, 0}}; + constexpr const char* SHORT_OPTIONS = "hf:m:i:l:c:t:"; + int32_t index; + int32_t opt{-1}; + while ((opt = getopt_long(argc, argv, SHORT_OPTIONS, LONG_OPTIONS, &index), opt != -1)) + { + m_errorFlag = false; + switch (opt) + { + case 'h': + { + std::cout << "Usage: " << argv[0] << " [options]" << std::endl; + std::cout << "Options:" << std::endl; + std::cout << "-h, --help Display help." << std::endl; + std::cout << "-f, --fuzzing-api Specify API which will be fuzzed." << std::endl; + std::cout << " {uds, com, toml}" << std::endl; + std::cout + << " uds: Starts RouDi and sends messages via Unix Domain " + "Sockets. Multiple messages can be sent. (e.g.: register message first and then offer service)." + << std::endl; + std::cout << " com: Invokes the processMessage method in RouDi " + "directly. This abstracts the IPC and is faster but multiple messages are not supported." + << std::endl; + std::cout + << " toml: Send inputs to test the TOML config file parser. A " + "file is created in your current working directory and the path is sent to the Parser." + << std::endl; + std::cout << "-m, --input-mode {stdin, cl}" << std::endl; + std::cout << " stdin: Send input via stdin." << std::endl; + std::cout << " cl: Send input via commandline. Needs parameter i " + "to send the input." + << std::endl; + std::cout << "-c, --command-line-file : Read the specified file and send " + "the input to the interface." + << std::endl; + std::cout + << "-i, --command-line-input : Send the input via this command line, requires " + "to use input-mode cl. It's possible to send several commands with several -i commands." + << std::endl; + std::cout + << "-t, --toml-file : Needs to be used when TOML is parsed. The " + "file is used to write messages which will be parsed by the TOML configuration parser." + << std::endl; + std::cout << "-l, --log-level {off, fatal, debug} : Set the log level. " + "Off is standard;" + << std::endl; + m_helpFlag = true; + } + break; - case 'm': - { - if (strcmp(optarg, "stdin") == 0) - { - inputMode = inputMode::STDIN; - FuzzHelper aFuzzHelper; - allMessages = aFuzzHelper.getStdInMessages(); - } - else if (strcmp(optarg, "cl") == 0) - { - inputMode = inputMode::CL; - } - else - { - std::cout << "Options for input-mode are 'stdin' and 'cl'!" << std::endl; - } - } break; - case 'i': - { - cmdLineFlag = true; - allMessages.emplace_back(optarg); + case 'f': + { + if (strcmp(optarg, "uds") == 0) + { + m_fuzzingAPI = FuzzingApi::UDS; + } + else if (strcmp(optarg, "com") == 0) + { + m_fuzzingAPI = FuzzingApi::COM; + } + else if (strcmp(optarg, "toml") == 0) + { + m_fuzzingAPI = FuzzingApi::TOML; + } + else + { + std::cout << "Options for fuzzing-api are 'uds', 'com' and 'toml'!" << std::endl; + m_errorFlag = true; + return m_allMessages; + } + } + break; - } break; + case 'm': + { + if (strcmp(optarg, "stdin") == 0) + { + m_inputMode = InputMode::STDIN; + FuzzHelper aFuzzHelper; + m_allMessages = aFuzzHelper.getStdInMessages(); + } + else if (strcmp(optarg, "cl") == 0) + { + m_inputMode = InputMode::CL; + } + else + { + std::cout << "Options for input-mode are 'stdin' and 'cl'!" << std::endl; + m_errorFlag = true; + return m_allMessages; + } + } + break; + case 'i': + { + m_cmdLineFlag = true; + m_allMessages.emplace_back(optarg); + } + break; - case 'c': - { - cmdLineFlag = true; - std::ifstream ifile; - ifile.open(optarg); - if(ifile) - { - std::string tempFileContent( (std::istreambuf_iterator(ifile) ), (std::istreambuf_iterator())); - allMessages.emplace_back(tempFileContent); - } + case 'c': + { + m_cmdLineFlag = true; + std::ifstream ifile; + ifile.open(optarg); + if (ifile) + { + std::string tempFileContent((std::istreambuf_iterator(ifile)), + (std::istreambuf_iterator())); + m_allMessages.emplace_back(tempFileContent); + } - else - { - std::cout<<"Error cannot open file. Either file does not exist or I don't have the permissions to open it."; - errorFlag = true; - return allMessages; - } - }break; + else + { + std::cout + << "Error cannot open file. Either file does not exist or I don't have the permissions to open it."; + m_errorFlag = true; + return m_allMessages; + } + } + break; - case 'l': - { - if (strcmp(optarg, "off") == 0) - { - m_logLevel = iox::log::LogLevel::kOff; - } - else if (strcmp(optarg, "fatal") == 0) - { - m_logLevel = iox::log::LogLevel::kFatal; - } - else if (strcmp(optarg, "debug") == 0) - { - m_logLevel = iox::log::LogLevel::kDebug; - } - else - { - std::cout << "Options for Logging are 'off', 'fatal' and 'debug'!" << std::endl; - } - iox::log::LogManager::GetLogManager().SetDefaultLogLevel(m_logLevel); - } break; - case 't': - { - tomlFileFlag = true; - tomlFile = optarg; - } break; - default: - { - std::cout << "Unknown command.\n" << std::endl; - errorFlag = true; - return allMessages; - } break; - }; - } - return allMessages; + case 'l': + { + if (strcmp(optarg, "off") == 0) + { + m_logLevel = iox::log::LogLevel::kOff; + } + else if (strcmp(optarg, "fatal") == 0) + { + m_logLevel = iox::log::LogLevel::kFatal; + } + else if (strcmp(optarg, "debug") == 0) + { + m_logLevel = iox::log::LogLevel::kDebug; + } + else + { + std::cout << "Options for Logging are 'off', 'fatal' and 'debug'!" << std::endl; + } + iox::log::LogManager::GetLogManager().SetDefaultLogLevel(m_logLevel); + } + break; + case 't': + { + m_tomlFileFlag = true; + m_tomlFile = optarg; + } + break; + default: + { + std::cout << "Unknown command.\n" << std::endl; + m_errorFlag = true; + return m_allMessages; + } + break; + }; + } + return m_allMessages; } diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp index 06262c9cc63..f03be209c12 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp @@ -15,46 +15,78 @@ // // SPDX-License-Identifier: Apache-2.0 -#include -#include +#ifndef CMDLINEPARSERFUZZING_HPP +#define CMDLINEPARSERFUZZING_HPP +#include +#include -enum class fuzzingAPI +enum class FuzzingApi { NONE, - UDS, + UDS, COM, - TOML + TOML }; -enum class inputMode +enum class InputMode { - NONE, + NONE, STDIN, CL }; -/// @brief CmdLineParserFuzzing is a class which parses the command lines to configure the Fuzz Wrappers for example to tell +/// @brief CmdLineParserFuzzing is a class which parses the command lines to configure the Fuzz Wrappers for +/// example to tell /// which interface shall be fuzzed. class CmdLineParserFuzzing { - public: - CmdLineParserFuzzing(); - /// @brief Parses the command line parameters whiich are entered by starting the fuzz wrappers - /// @param[in] amount of arguments given to the method - /// @param[in] containing the command line parameters - /// @param[out] Containing the messages which shall be sent to the interface - std::vector parseCmd(int argc, char* argv[]) noexcept; - - bool helpFlag; - inputMode inputMode; - bool errorFlag; - bool cmdLineFlag; - fuzzingAPI fuzzingAPI; - bool tomlFileFlag; - std::string tomlFile; - - private: - - std::vector allMessages; - }; + public: + CmdLineParserFuzzing(); + + /// @brief Parses the command line parameters whiich are entered by starting the fuzz wrappers + /// @param[in] amount of arguments given to the method + /// @param[in] containing the command line parameters + /// @param[out] Containing the messages which shall be sent to the interface + std::vector parseCmd(int argc, char* argv[]) noexcept; + + /// @brief Getter to return m_helpFlag + /// @param[out] Containing a flag showing if the help menu was displayed. + bool getHelpFlag(); + + /// @brief Getter to return m inputMode + /// @param[out] Containing enum InputMode to show if messages are sent to the APi via stdin or command line (cl). + InputMode getInputMode(); + + /// @brief Getter to return m_errorFlag + /// @param[out] Containing a flag showing if an error happened and fuzzing cannot be started + bool getErrorFlag(); + + /// @brief Getter to return m_cmdLineFlag + /// @param[out] Containing a flag showing if a command line parameter was given after InputMode::CL was set + bool getCmdLineFlag(); + + /// @brief Getter to return m_fuzzingAPI + /// @param[out] Containing enum FuzzingFlag indicating which API wants to be fuzzed. + FuzzingApi getFuzzingAPI(); + + /// @brief Getter to return m_tomlFileFlag + /// @param[out] Containing a flag showing if TOML API wants to be fuzzed. + bool getTomlFileFlag(); + + /// @brief Getter to return m_tomlFile + /// @param[out] Containing an std::string to a file which can be used to temporarily write a TOML configuration to + ///the file. + std::string getTomlFile(); + + private: + std::vector m_allMessages; + bool m_helpFlag; + InputMode m_inputMode; + bool m_errorFlag; + bool m_cmdLineFlag; + FuzzingApi m_fuzzingAPI; + bool m_tomlFileFlag; + std::string m_tomlFile; +}; +#endif // CMDLINEPARSERFUZZING_HPP diff --git a/iceoryx_posh/test/fuzztests/fuzz_helper.cpp b/iceoryx_posh/test/fuzztests/fuzz_helper.cpp index d3cf3d9749a..a306b15104b 100644 --- a/iceoryx_posh/test/fuzztests/fuzz_helper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzz_helper.cpp @@ -1,5 +1,4 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,44 +21,45 @@ std::vector FuzzHelper::getStdInMessages() { - std::vector stdInMessages; - for (std::string line; std::getline(std::cin, line);) - { - stdInMessages.push_back(line); - } - return stdInMessages; + std::vector stdInMessages; + for (std::string line; std::getline(std::cin, line);) + { + stdInMessages.push_back(line); + } + return stdInMessages; } std::shared_ptr FuzzHelper::startRouDiThread() { - static iox::roudi::IceOryxRouDiComponents m_rouDiComponents(iox::RouDiConfig_t().setDefaults()); - static iox::RouDiConfig_t m_config = iox::RouDiConfig_t().setDefaults(); - std::shared_ptr aRouDi(new RouDiFuzz(m_rouDiComponents.m_rouDiMemoryManager, m_rouDiComponents.m_portManager)); - return aRouDi; + static iox::roudi::IceOryxRouDiComponents m_rouDiComponents(iox::RouDiConfig_t().setDefaults()); + static iox::RouDiConfig_t m_config = iox::RouDiConfig_t().setDefaults(); + std::shared_ptr aRouDi( + new RouDiFuzz(m_rouDiComponents.m_rouDiMemoryManager, m_rouDiComponents.m_portManager)); + return aRouDi; } bool FuzzHelper::checkIsRouDiRunning() { - Fuzzing aFuzzer; - int udsStatus = aFuzzer.fuzzingRouDiUDS("Hello Roudi!"); - if (udsStatus == -1) - { - return false; - } - else - { - return true; - } + Fuzzing aFuzzer; + int udsStatus = aFuzzer.fuzzingRouDiUDS("Hello Roudi!"); + if (udsStatus == -1) + { + return false; + } + else + { + return true; + } } std::vector FuzzHelper::combineString(std::vector allMessages) { - std::string tempString = ""; - for(std::string aMessage: allMessages) - { - tempString += aMessage + "\n"; - } - allMessages.clear(); - allMessages.emplace_back(tempString); - return allMessages; + std::string tempString = ""; + for (std::string aMessage : allMessages) + { + tempString += aMessage + "\n"; + } + allMessages.clear(); + allMessages.emplace_back(tempString); + return allMessages; } diff --git a/iceoryx_posh/test/fuzztests/fuzz_helper.hpp b/iceoryx_posh/test/fuzztests/fuzz_helper.hpp index e4256552b8e..b5165fe70dc 100644 --- a/iceoryx_posh/test/fuzztests/fuzz_helper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzz_helper.hpp @@ -1,5 +1,4 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved.ved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,34 +14,33 @@ // // SPDX-License-Identifier: Apache-2.0 -#include #include "roudi_fuzz.hpp" +#include /// @brief Class to implement some help methods for the fuzz wrapper class FuzzHelper { + public: + /// @brief Reads messages from stdin and writes them into a std::vector + /// @param[in] message via stdin + /// @param[out] std::vector containing std::strings of the messages from stdin. Each std::string in the vector + /// is one line of stdin. This means that if there is one newline in stdin, there will be two std::strings, with two + /// newlines, there will be three meessages,... + std::vector getStdInMessages(); - public: - - /// @brief Reads messages from stdin and writes them into a std::vector - /// @param[in] message via stdin - /// @param[out] std::vector containing std::strings of the messages from stdin. Each std::string in the vector is one line of stdin. - /// this means that if there is one newline in stdin, there will be two std::strings, with two newlines, there will be three - /// meessages,... - std::vector getStdInMessages(); - - /// @brief a shared Ptr to a RouDi thread which will be used to keep the thread alive until the message is processed by RouDi - /// @param[out] a shated_ptr to RouDiFuzz which inherits from RouDi - std::shared_ptr startRouDiThread(); - - /// @brief Splitted messages in allMessages are put together as one String. This is used for TOML parser for example because one message - /// can contain newlines - /// @param[in] std::vector containing several std::string messages which shall be sent to an interface - /// @param[out] std::vector containing one std::string message - std::vector combineString(std::vector allMessages); - - /// @brief A method to check if RouDi is alive. It checks if the UDS is available and then sends a default message to RouDi - /// @param[out] Boolean value indicating if RouDi is available - bool checkIsRouDiRunning(); + /// @brief a shared Ptr to a RouDi thread which will be used to keep the thread alive until the message is + /// processed by RouDi + /// @param[out] a shated_ptr to RouDiFuzz which inherits from RouDi + std::shared_ptr startRouDiThread(); + /// @brief Splitted messages in allMessages are put together as one String. This is used for TOML parser for + /// example because one message + /// can contain newlines + /// @param[in] std::vector containing several std::string messages which shall be sent to an interface + /// @param[out] std::vector containing one std::string message + std::vector combineString(std::vector allMessages); + /// @brief A method to check if RouDi is alive. It checks if the UDS is available and then sends a default message + /// to RouDi + /// @param[out] Boolean value indicating if RouDi is available + bool checkIsRouDiRunning(); }; diff --git a/iceoryx_posh/test/fuzztests/fuzzing.cpp b/iceoryx_posh/test/fuzztests/fuzzing.cpp index 15104284164..24f87a3e7f6 100644 --- a/iceoryx_posh/test/fuzztests/fuzzing.cpp +++ b/iceoryx_posh/test/fuzztests/fuzzing.cpp @@ -1,5 +1,4 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,6 +18,7 @@ #include #include "cpptoml.h" #include +#include std::string const UDS_NAME = "/tmp/"; @@ -50,7 +50,7 @@ int Fuzzing::fuzzingRouDiUDS(std::string aMessage) if (connectfd != -1) { sendto(sockfd, aMessage.c_str(), aMessage.length()+1, static_cast(0), nullptr, static_cast(0)); - usleep(500000); //0.5 second We need to wait otherwise it can happen that RouDi did not process the message + std::this_thread::sleep_for(std::chrono::milliseconds(500));; //0.5 second We need to wait otherwise it can happen that RouDi did not process the message } else { diff --git a/iceoryx_posh/test/fuzztests/fuzzing.hpp b/iceoryx_posh/test/fuzztests/fuzzing.hpp index 7637181815c..5a1b2d58448 100644 --- a/iceoryx_posh/test/fuzztests/fuzzing.hpp +++ b/iceoryx_posh/test/fuzztests/fuzzing.hpp @@ -1,5 +1,4 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,10 +14,6 @@ // // SPDX-License-Identifier: Apache-2.0 - - - - #ifndef FUZZING_HPP #define FUZZING_HPP @@ -26,30 +21,35 @@ #include #include -/// @brief Fuzzing is a class which is used to send a message to different interfaces which shall be fuzzed. +/// @brief Fuzzing is a class which is used to send a message to different interfaces which shall be fuzzed. class Fuzzing { - public: - Fuzzing(); - - /// @brief fuzzingRouDiCom is a method to test the processMessage method of RouDI. It shall be independent from the underlying protocol - /// such as uds. It should also be slightly faster since some functions are not invoked compared to uds fuzzing. - /// However, a RouDi thread is also started with this approach because otherwise it was not be possible to invoke - /// the processMessage method within RouDi without directly modifying the code in RouDi. - /// @param [in] shared_ptr to RouDiFuzz such that processMessage can be invoked - /// @param [in] std::string containing a message which shall be sent to the interface - void fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage); - - /// @brief fuzzingRouDiUDS is a method to test the Unix Domain Socket interface of RouDI. It connects to RouDi's uds and sends the message - /// given as input to RouDi - /// @param [in] std::string containing a message which shall be sent to the interface - /// @param [out] int as result of connect(). If the int = -1 it means that it was not possible to connect to the socket - int fuzzingRouDiUDS(std::string aMessage); - - /// @brief fuzzingTOMLParser is a method to send a message to the TOML parser. - /// @param [in] std::string containing a message which shall be sent to the interface - /// @param [in] std::string containing a valid path to an empty file which will be used to write the tomlFile into. This is necessary - /// because the TOML parser expects a path to a file as input - void fuzzingTOMLParser(std::string tomlFile, std::string tempFile); + public: + Fuzzing(); + + /// @brief fuzzingRouDiCom is a method to test the processMessage method of RouDI. It shall be independent from + /// the underlying protocol + /// such as uds. It should also be slightly faster since some functions are not invoked compared to uds + /// fuzzing. However, a RouDi thread is also started with this approach because otherwise it was not + /// be possible to + /// invoke the processMessage method within RouDi without directly modifying the code in RouDi. + /// @param [in] shared_ptr to RouDiFuzz such that processMessage can be invoked + /// @param [in] std::string containing a message which shall be sent to the interface + void fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage); + + /// @brief fuzzingRouDiUDS is a method to test the Unix Domain Socket interface of RouDI. It connects to + /// RouDi's uds and sends the message + /// given as input to RouDi + /// @param [in] std::string containing a message which shall be sent to the interface + /// @param [out] int as result of connect(). If the int = -1 it means that it was not possible to connect to the + /// socket + int fuzzingRouDiUDS(std::string aMessage); + + /// @brief fuzzingTOMLParser is a method to send a message to the TOML parser. + /// @param [in] std::string containing a message which shall be sent to the interface + /// @param [in] std::string containing a valid path to an empty file which will be used to write the tomlFile + /// into. This is necessary + /// because the TOML parser expects a path to a file as input + void fuzzingTOMLParser(std::string tomlFile, std::string tempFile); }; #endif /* FUZZING_HPP */ diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index 9512ec7dd92..c5c658898cb 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,132 +11,138 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 -#include "iceoryx_posh/internal/roudi/roudi.hpp" -#include "iceoryx_posh/internal/log/posh_logging.hpp" -#include "iceoryx_posh/roudi/iceoryx_roudi_components.hpp" #include "fuzztests_roudi_wrapper.hpp" -#include "fuzzing.hpp" -#include "fuzz_helper.hpp" #include "cmdlineparserfuzzing.hpp" +#include "fuzz_helper.hpp" +#include "fuzzing.hpp" +#include "iceoryx_posh/internal/log/posh_logging.hpp" +#include "iceoryx_posh/internal/roudi/roudi.hpp" +#include "iceoryx_posh/roudi/iceoryx_roudi_components.hpp" #include #include +#include -unsigned const char TIMEOUT = 50; //5s for a Timeout +unsigned const char TIMEOUT = 50; // 5s for a Timeout #define INTERFACE_NAME "/test" -int main (int argc, char* argv[]) +int main(int argc, char* argv[]) { - CmdLineParserFuzzing cmd; - std::vector allMessages = cmd.parseCmd(argc, argv); - - if(cmd.helpFlag == true) - { - return 1; - } - - if(cmd.errorFlag == true) - { - std::cout << "No or wrong command lines were specified. Please use --help!" << std::endl; - return -1; - } - - if(allMessages.empty()) - { - std::cout << "Please use -m [cl, stdin] to enter the input you want to send to the executable. If you use -m cl, then you also need use -i [INPUT_MESSAGE] or -c [PATH_To_File] to specify the message." << std::endl; - return -1; - } - - if (cmd.inputMode == inputMode::NONE) - { - std::cout << "Please use -m to specify the input. Please use --help to get more information." << std::endl; - return -1; - } - - if(cmd.inputMode == inputMode::CL and !cmd.cmdLineFlag) - { - std::cout << "Please use -i [INPUT_MESSAGE] or -c [PATH_To_File] to enter a String which you want to send to the interface. It is also possible to use -m stdin instead." << std::endl; - return -1; - } - FuzzHelper aFuzzHelper; - std::shared_ptr aRouDi; - - if (cmd.fuzzingAPI == fuzzingAPI::TOML) - { - if(!cmd.tomlFileFlag) - { - std::cout << "Please use -t [PATH_To_File] to specify a file where the messages are written to which are sent to the TOML configuration parser." << std::endl; - return -1; - } - else - { - allMessages = aFuzzHelper.combineString(allMessages); - } - } - - if (cmd.fuzzingAPI == fuzzingAPI::UDS or cmd.fuzzingAPI == fuzzingAPI::COM) // Start RouDi - { - aRouDi = aFuzzHelper.startRouDiThread(); - unsigned char timeout = 0; - while(!aFuzzHelper.checkIsRouDiRunning()) - { - if(timeout >= TIMEOUT) - { - std::cout << "RouDI could not be started, program terminates!" << std::endl; - return -1; - } - usleep(100000); //1/10 of a second - timeout += 1; - } - } - - if(cmd.inputMode == inputMode::CL or cmd.inputMode == inputMode::STDIN) - { - Fuzzing aFuzzer; - for(std::string aMessage: allMessages) - { - - switch (cmd.fuzzingAPI) - { - case fuzzingAPI::COM: - { - iox::LogDebug() << "Messages sent to RouDi: " << aMessage; - aFuzzer.fuzzingRouDiCom(aRouDi, aMessage); - } break; - - case fuzzingAPI::UDS: - { - aFuzzer.fuzzingRouDiUDS(aMessage); - iox::LogDebug() << "Messages sent to RouDi: " << aMessage; - } break; - - case fuzzingAPI::TOML: - { - aFuzzer.fuzzingTOMLParser(aMessage, cmd.tomlFile); - iox::LogDebug() << "Messages sent to TOML Parser: " << aMessage; - } break; - - default: - { - - std::cout << "Error: Unkown Fuzzing API parameter" << std::endl; - return -1; - - } break; - }; - } - - } - - else - { - std::cout << "Error: Only stdin and command line are allowed to enter an input. Please use --help to get more information." << std::endl; - return -1; - } - + CmdLineParserFuzzing cmd; + std::vector allMessages = cmd.parseCmd(argc, argv); + + if (cmd.getHelpFlag() == true) + { + return 1; + } + + if (cmd.getErrorFlag() == true) + { + std::cout << "No or wrong command lines were specified. Please use --help!" << std::endl; + return -1; + } + + if (allMessages.empty()) + { + std::cout << "Please use -m [cl, stdin] to enter the input you want to send to the executable. If you use -m " + "cl, then you also need use -i [INPUT_MESSAGE] or -c [PATH_To_File] to specify the message." + << std::endl; + return -1; + } + + if (cmd.getInputMode() == InputMode::NONE) + { + std::cout << "Please use -m to specify the input. Please use --help to get more information." << std::endl; + return -1; + } + + if (cmd.getInputMode() == InputMode::CL and !cmd.getCmdLineFlag()) + { + std::cout << "Please use -i [INPUT_MESSAGE] or -c [PATH_To_File] to enter a String which you want to send to " + "the interface. It is also possible to use -m stdin instead." + << std::endl; + return -1; + } + FuzzHelper aFuzzHelper; + std::shared_ptr aRouDi; + + if (cmd.getFuzzingAPI() == FuzzingApi::TOML) + { + if (!cmd.getTomlFileFlag()) + { + std::cout << "Please use -t [PATH_To_File] to specify a file where the messages are written to which are " + "sent to the TOML configuration parser." + << std::endl; + return -1; + } + else + { + allMessages = aFuzzHelper.combineString(allMessages); + } + } + + if (cmd.getFuzzingAPI() == FuzzingApi::UDS or cmd.getFuzzingAPI() == FuzzingApi::COM) // Start RouDi + { + aRouDi = aFuzzHelper.startRouDiThread(); + unsigned char timeout = 0; + while (!aFuzzHelper.checkIsRouDiRunning()) + { + if (timeout >= TIMEOUT) + { + std::cout << "RouDI could not be started, program terminates!" << std::endl; + return -1; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 1/10 of a second + timeout += 1; + } + } + + if (cmd.getInputMode() == InputMode::CL or cmd.getInputMode() == InputMode::STDIN) + { + Fuzzing aFuzzer; + for (std::string aMessage : allMessages) + { + switch (cmd.getFuzzingAPI()) + { + case FuzzingApi::COM: + { + iox::LogDebug() << "Messages sent to RouDi: " << aMessage; + aFuzzer.fuzzingRouDiCom(aRouDi, aMessage); + } + break; + + case FuzzingApi::UDS: + { + aFuzzer.fuzzingRouDiUDS(aMessage); + iox::LogDebug() << "Messages sent to RouDi: " << aMessage; + } + break; + + case FuzzingApi::TOML: + { + aFuzzer.fuzzingTOMLParser(aMessage, cmd.getTomlFile()); + iox::LogDebug() << "Messages sent to TOML Parser: " << aMessage; + } + break; + + default: + { + std::cout << "Error: Unkown Fuzzing API parameter" << std::endl; + return -1; + } + break; + }; + } + } + + else + { + std::cout << "Error: Only stdin and command line are allowed to enter an input. Please use --help to get more " + "information." + << std::endl; + return -1; + } } - - - - diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp index 4ce06688d8d..8fcad92e8e5 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp @@ -1,5 +1,4 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,6 +14,9 @@ // // SPDX-License-Identifier: Apache-2.0 +#ifndef FUZZTESTSROUDIWRAPPER_HPP +#define FUZZTESTSROUDIWRAPPER_HPP + /// @brief fuzztests_roudi_wrapper contains the main method for the RouDi Wrappers which can be used to fuzz several interfaces /// @brief Main function of the Fuzz Wrapper @@ -23,6 +25,7 @@ /// @param[out] int containing the status if the fuzzing was successfull. If the return value is -1 something went wrong. int main (int argc, char* argv[]); +#endif /*FUZZTESTSROUDIWRAPPE*/ diff --git a/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp b/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp index 24f777d5fa3..d492616e6b0 100644 --- a/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp +++ b/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp @@ -1,5 +1,4 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,15 +16,18 @@ #include "roudi_fuzz.hpp" #include "string_to_ipc_message.hpp" -RouDiFuzz::RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, iox::roudi::PortManager& portManager, iox::roudi::RouDi::RoudiStartupParameters aStartupParameter) : iox::roudi::RouDi(roudiMemoryInterface, portManager, aStartupParameter) +RouDiFuzz::RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, + iox::roudi::PortManager& portManager, + iox::roudi::RouDi::RoudiStartupParameters aStartupParameter) + : iox::roudi::RouDi(roudiMemoryInterface, portManager, aStartupParameter) { } void RouDiFuzz::processMessageFuzz(std::string aMessage) { - iox::runtime::IpcMessage ipcMessage; - StringToIPCMessage::setMessageFromString(aMessage.c_str(), ipcMessage); - iox::runtime::IpcMessageType cmd = iox::runtime::stringToIpcMessageType(ipcMessage.getElementAtIndex(0).c_str()); - std::string processName = ipcMessage.getElementAtIndex(1); - iox::roudi::RouDi::processMessage(ipcMessage, cmd, iox::ProcessName_t(iox::cxx::TruncateToCapacity, processName)); + iox::runtime::IpcMessage ipcMessage; + StringToIPCMessage::setMessageFromString(aMessage.c_str(), ipcMessage); + iox::runtime::IpcMessageType cmd = iox::runtime::stringToIpcMessageType(ipcMessage.getElementAtIndex(0).c_str()); + std::string processName = ipcMessage.getElementAtIndex(1); + iox::roudi::RouDi::processMessage(ipcMessage, cmd, iox::ProcessName_t(iox::cxx::TruncateToCapacity, processName)); } diff --git a/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp b/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp index 661158d61b5..e7cba86738c 100644 --- a/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp +++ b/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,21 +14,27 @@ // // SPDX-License-Identifier: Apache-2.0 -#ifndef ROUDI_FUZZ_HPP -#define ROUDI_FUZZ_HPP +#ifndef ROUDIFUZZ_HPP +#define ROUDIFUZZ_HPP #include "iceoryx_posh/internal/roudi/roudi.hpp" -/// @brief RouDiFuzz is a class which inherits from iox::roudi::RouDi to make some protected methods avaialble for Fuzzing. This is necessary, to directly injects messages +/// @brief RouDiFuzz is a class which inherits from iox::roudi::RouDi to make some protected methods avaialble for +/// Fuzzing. This is necessary, to directly injects messages /// in these messages to test the robustness of the interfaces class RouDiFuzz : iox::roudi::RouDi { - public: - RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, iox::roudi::PortManager& portManager, iox::roudi::RouDi::RoudiStartupParameters = {iox::roudi::MonitoringMode::OFF, false, iox::roudi::RouDi::RuntimeMessagesThreadStart::IMMEDIATE, iox::version::CompatibilityCheckLevel::OFF}); - - /// @brief [in] Send a message to the processMessage method of RouDi - /// @param [in] Message which should be sent to the processMessage method of RouDi - void processMessageFuzz(std::string aMessage); + public: + RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, + iox::roudi::PortManager& portManager, + iox::roudi::RouDi::RoudiStartupParameters = {iox::roudi::MonitoringMode::OFF, + false, + iox::roudi::RouDi::RuntimeMessagesThreadStart::IMMEDIATE, + iox::version::CompatibilityCheckLevel::OFF}); + + /// @brief [in] Send a message to the processMessage method of RouDi + /// @param [in] Message which should be sent to the processMessage method of RouDi + void processMessageFuzz(std::string aMessage); }; -#endif //ROUDI_FUZZ_HPP +#endif /*ROUDIFUZZ_HPP*/ diff --git a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp index e4948c37986..76006b13a12 100644 --- a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp +++ b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,16 +14,22 @@ // // SPDX-License-Identifier: Apache-2.0 -/// @brief The StringToIPCMessage is a class which inherits from iox::runtime::IpcInterfaceBase to make the protected method -/// iox::runtime::IpcInterfaceBase::setMessageFromString public and accessible for the fuzz test. +#ifndef STRINGTOIPCMESSAGE_HPP +#define STRINGTOIPCMESSAGE_HPP + +/// @brief The StringToIPCMessage is a class which inherits from iox::runtime::IpcInterfaceBase to make the protected +/// method +/// iox::runtime::IpcInterfaceBase::setMessageFromString public and accessible for the fuzz test. class StringToIPCMessage : public iox::runtime::IpcInterfaceBase { - public: - StringToIPCMessage(const iox::ProcessName_t& name, const int64_t maxMessages, const int64_t messageSize); - - /// @brief Set the content of answer from buffer. - /// @param[in] buffer Raw message as char pointer - /// @param[out] answer Raw message is setting this IpcMessage - /// @return answer.isValid() - using iox::runtime::IpcInterfaceBase::setMessageFromString; -}; \ No newline at end of file + public: + StringToIPCMessage(const iox::ProcessName_t& name, const int64_t maxMessages, const int64_t messageSize); + + /// @brief Set the content of answer from buffer. + /// @param[in] buffer Raw message as char pointer + /// @param[out] answer Raw message is setting this IpcMessage + /// @return answer.isValid() + using iox::runtime::IpcInterfaceBase::setMessageFromString; +}; + +#endif /*STRINGTOIPCMESSAGE*/ From 8fb4961472afaccb565e4e5e2ce4e0f73652414a Mon Sep 17 00:00:00 2001 From: "Mueller Julian (CC-AD/EYF1)" Date: Wed, 31 Mar 2021 12:45:05 +0200 Subject: [PATCH 007/127] iox-558 Small typo corrections. Signed-off-by: Mueller Julian (CC-AD/EYF1) --- iceoryx_posh/test/fuzztests/fuzz_helper.hpp | 10 ++- iceoryx_posh/test/fuzztests/fuzzing.cpp | 82 +++++++++++---------- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/fuzz_helper.hpp b/iceoryx_posh/test/fuzztests/fuzz_helper.hpp index b5165fe70dc..24d055d2097 100644 --- a/iceoryx_posh/test/fuzztests/fuzz_helper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzz_helper.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved.ved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,12 +14,16 @@ // // SPDX-License-Identifier: Apache-2.0 +#ifndef FUZZHELPER_HPP +#define FUZZHELPER_HPP + #include "roudi_fuzz.hpp" #include + /// @brief Class to implement some help methods for the fuzz wrapper class FuzzHelper { - public: + public: /// @brief Reads messages from stdin and writes them into a std::vector /// @param[in] message via stdin /// @param[out] std::vector containing std::strings of the messages from stdin. Each std::string in the vector @@ -44,3 +48,5 @@ class FuzzHelper /// @param[out] Boolean value indicating if RouDi is available bool checkIsRouDiRunning(); }; + +#endif /*FUZZHELPER_HPP*/ diff --git a/iceoryx_posh/test/fuzztests/fuzzing.cpp b/iceoryx_posh/test/fuzztests/fuzzing.cpp index 24f87a3e7f6..4c78315f489 100644 --- a/iceoryx_posh/test/fuzztests/fuzzing.cpp +++ b/iceoryx_posh/test/fuzztests/fuzzing.cpp @@ -15,8 +15,8 @@ // SPDX-License-Identifier: Apache-2.0 #include "fuzzing.hpp" -#include #include "cpptoml.h" +#include #include #include @@ -24,55 +24,57 @@ std::string const UDS_NAME = "/tmp/"; Fuzzing::Fuzzing() { - } void Fuzzing::fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage) { - if(aRouDi != nullptr) - { - aRouDi->processMessageFuzz(aMessage); - } - else - { - iox::LogDebug() << "Error, the Smart Pointer for RouDI which is used to call the method 'processMessage' is NULL"; - } + if (aRouDi != nullptr) + { + aRouDi->processMessageFuzz(aMessage); + } + else + { + iox::LogDebug() + << "Error, the Smart Pointer for RouDI which is used to call the method 'processMessage' is NULL"; + } } int Fuzzing::fuzzingRouDiUDS(std::string aMessage) { - int sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); - struct sockaddr aSockAddr; - aSockAddr.sa_family = AF_LOCAL; - std::string roudiName = UDS_NAME + iox::roudi::IPC_CHANNEL_ROUDI_NAME; - strncpy(aSockAddr.sa_data, roudiName.c_str(), sizeof(aSockAddr.sa_data)); - int connectfd = connect(sockfd, &aSockAddr, sizeof(aSockAddr)); - if (connectfd != -1) - { - sendto(sockfd, aMessage.c_str(), aMessage.length()+1, static_cast(0), nullptr, static_cast(0)); - std::this_thread::sleep_for(std::chrono::milliseconds(500));; //0.5 second We need to wait otherwise it can happen that RouDi did not process the message - } - else - { - iox::LogDebug() << "Could not connect to RoudI"; - } - return connectfd; + int sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0); + struct sockaddr aSockAddr; + aSockAddr.sa_family = AF_LOCAL; + std::string roudiName = UDS_NAME + iox::roudi::IPC_CHANNEL_ROUDI_NAME; + strncpy(aSockAddr.sa_data, roudiName.c_str(), sizeof(aSockAddr.sa_data)); + int connectfd = connect(sockfd, &aSockAddr, sizeof(aSockAddr)); + if (connectfd != -1) + { + sendto( + sockfd, aMessage.c_str(), aMessage.length() + 1, static_cast(0), nullptr, static_cast(0)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + // 0.5 second We need to wait otherwise it can happen that RouDi did not process the message + } + else + { + iox::LogDebug() << "Could not connect to RoudI"; + } + return connectfd; } - + void Fuzzing::fuzzingTOMLParser(std::string aMessage, std::string tempFile) { - std::ofstream aTomlFile; - aTomlFile.open(tempFile); - std::cout << "Sent to TOML: " << aMessage << std::endl; - if(aTomlFile.is_open()) - { - aTomlFile << aMessage; - cpptoml::parse_file(tempFile); - aTomlFile.close(); - } - else - { - iox::LogDebug() << "Cannot open file to send it to TOML Parser: " << tempFile; - } + std::ofstream aTomlFile; + aTomlFile.open(tempFile); + std::cout << "Sent to TOML: " << aMessage << std::endl; + if (aTomlFile.is_open()) + { + aTomlFile << aMessage; + cpptoml::parse_file(tempFile); + aTomlFile.close(); + } + else + { + iox::LogDebug() << "Cannot open file to send it to TOML Parser: " << tempFile; + } } From 8a7f649613a90044ac8984b2f3506db8adad2b9d Mon Sep 17 00:00:00 2001 From: "Mueller Julian (CC-AD/EYF1)" Date: Wed, 31 Mar 2021 13:22:45 +0200 Subject: [PATCH 008/127] iox-558 small corrections with code readability and copyright Signed-off-by: Mueller Julian (CC-AD/EYF1) --- .../test/fuzztests/cmdlineparserfuzzing.hpp | 5 ++--- .../test/fuzztests/fuzztests_roudi_wrapper.hpp | 16 +++++----------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp index f03be209c12..e973dc9b597 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp @@ -1,5 +1,4 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2021 by Robert Bosch GmbH. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -43,7 +42,7 @@ class CmdLineParserFuzzing { public: CmdLineParserFuzzing(); - + /// @brief Parses the command line parameters whiich are entered by starting the fuzz wrappers /// @param[in] amount of arguments given to the method /// @param[in] containing the command line parameters diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp index 8fcad92e8e5..d13a5e08ae3 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp @@ -13,22 +13,16 @@ // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 - #ifndef FUZZTESTSROUDIWRAPPER_HPP #define FUZZTESTSROUDIWRAPPER_HPP - -/// @brief fuzztests_roudi_wrapper contains the main method for the RouDi Wrappers which can be used to fuzz several interfaces +/// @brief fuzztests_roudi_wrapper contains the main method for the RouDi Wrappers which can be used to fuzz several +/// interfaces /// @brief Main function of the Fuzz Wrapper /// @param[in] amount of arguments given to the method /// @param[in] containing the command line parameters -/// @param[out] int containing the status if the fuzzing was successfull. If the return value is -1 something went wrong. -int main (int argc, char* argv[]); +/// @param[out] int containing the status if the fuzzing was successfull. If the return value is -1 something went +/// wrong. +int main(int argc, char* argv[]); #endif /*FUZZTESTSROUDIWRAPPE*/ - - - - - - From 4ad65e7ddfa16155537b3a3663c4b5aac85bab4c Mon Sep 17 00:00:00 2001 From: "Mueller Julian (CC-AD/EYF1)" Date: Wed, 31 Mar 2021 13:25:15 +0200 Subject: [PATCH 009/127] iox-558 small corrections with code readability Signed-off-by: Mueller Julian (CC-AD/EYF1) --- iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp index d13a5e08ae3..bba83ffdcc0 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp @@ -13,6 +13,7 @@ // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 + #ifndef FUZZTESTSROUDIWRAPPER_HPP #define FUZZTESTSROUDIWRAPPER_HPP /// @brief fuzztests_roudi_wrapper contains the main method for the RouDi Wrappers which can be used to fuzz several From 962a24b3f6b07642960c3a7cf5178beb80e12160 Mon Sep 17 00:00:00 2001 From: "Mueller Julian (CC-AD/EYF1)" Date: Wed, 7 Apr 2021 10:57:08 +0200 Subject: [PATCH 010/127] iox-558 Improvements in code documentation format. Small non-functional changes. Removed code which is not needed. Signed-off-by: Mueller Julian (CC-AD/EYF1) --- .../test/fuzztests/cmdlineparserfuzzing.cpp | 2 +- .../test/fuzztests/cmdlineparserfuzzing.hpp | 7 ++-- iceoryx_posh/test/fuzztests/fuzz_helper.hpp | 7 ++-- iceoryx_posh/test/fuzztests/fuzzing.hpp | 34 ++++++++----------- .../fuzztests/fuzztests_roudi_wrapper.cpp | 8 ++--- .../fuzztests/fuzztests_roudi_wrapper.hpp | 10 +++--- iceoryx_posh/test/fuzztests/roudi_fuzz.hpp | 7 ++-- .../test/fuzztests/string_to_ipc_message.hpp | 4 +-- 8 files changed, 35 insertions(+), 44 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp index fd77b55001f..ab75b420bcc 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp @@ -81,7 +81,7 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) {nullptr, 0, nullptr, 0}}; constexpr const char* SHORT_OPTIONS = "hf:m:i:l:c:t:"; int32_t index; - int32_t opt{-1}; + int opt{-1}; while ((opt = getopt_long(argc, argv, SHORT_OPTIONS, LONG_OPTIONS, &index), opt != -1)) { m_errorFlag = false; diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp index e973dc9b597..bf66e4d6411 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp @@ -35,9 +35,8 @@ enum class InputMode CL }; -/// @brief CmdLineParserFuzzing is a class which parses the command lines to configure the Fuzz Wrappers for -/// example to tell -/// which interface shall be fuzzed. +/// @brief CmdLineParserFuzzing is a class which parses the command lines to configure the Fuzz Wrappers for +/// example to tell which interface shall be fuzzed. class CmdLineParserFuzzing { public: @@ -75,7 +74,7 @@ class CmdLineParserFuzzing /// @brief Getter to return m_tomlFile /// @param[out] Containing an std::string to a file which can be used to temporarily write a TOML configuration to - ///the file. + /// the file. std::string getTomlFile(); private: diff --git a/iceoryx_posh/test/fuzztests/fuzz_helper.hpp b/iceoryx_posh/test/fuzztests/fuzz_helper.hpp index 24d055d2097..e5d29d14cc7 100644 --- a/iceoryx_posh/test/fuzztests/fuzz_helper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzz_helper.hpp @@ -28,7 +28,7 @@ class FuzzHelper /// @param[in] message via stdin /// @param[out] std::vector containing std::strings of the messages from stdin. Each std::string in the vector /// is one line of stdin. This means that if there is one newline in stdin, there will be two std::strings, with two - /// newlines, there will be three meessages,... + /// newlines, there will be three messages,... std::vector getStdInMessages(); /// @brief a shared Ptr to a RouDi thread which will be used to keep the thread alive until the message is @@ -37,8 +37,7 @@ class FuzzHelper std::shared_ptr startRouDiThread(); /// @brief Splitted messages in allMessages are put together as one String. This is used for TOML parser for - /// example because one message - /// can contain newlines + /// example because one message can contain newlines /// @param[in] std::vector containing several std::string messages which shall be sent to an interface /// @param[out] std::vector containing one std::string message std::vector combineString(std::vector allMessages); @@ -49,4 +48,4 @@ class FuzzHelper bool checkIsRouDiRunning(); }; -#endif /*FUZZHELPER_HPP*/ +#endif /*FUZZHELPER_HPP*/ \ No newline at end of file diff --git a/iceoryx_posh/test/fuzztests/fuzzing.hpp b/iceoryx_posh/test/fuzztests/fuzzing.hpp index 5a1b2d58448..e0f81cd685e 100644 --- a/iceoryx_posh/test/fuzztests/fuzzing.hpp +++ b/iceoryx_posh/test/fuzztests/fuzzing.hpp @@ -21,35 +21,31 @@ #include #include -/// @brief Fuzzing is a class which is used to send a message to different interfaces which shall be fuzzed. +/// @brief Fuzzing is a class which is used to send a message to different interfaces which shall be fuzzed. class Fuzzing { public: Fuzzing(); - /// @brief fuzzingRouDiCom is a method to test the processMessage method of RouDI. It shall be independent from - /// the underlying protocol - /// such as uds. It should also be slightly faster since some functions are not invoked compared to uds - /// fuzzing. However, a RouDi thread is also started with this approach because otherwise it was not - /// be possible to - /// invoke the processMessage method within RouDi without directly modifying the code in RouDi. - /// @param [in] shared_ptr to RouDiFuzz such that processMessage can be invoked - /// @param [in] std::string containing a message which shall be sent to the interface + /// @brief fuzzingRouDiCom is a method to test the processMessage method of RouDI. It shall be independent from + /// the underlying protocol such as uds. It should also be slightly faster since some functions are not invoked + /// compared to uds fuzzing. However, a RouDi thread is also started with this approach because otherwise it was not + /// be possible to invoke the processMessage method within RouDi without directly modifying the code in RouDi. + /// @param[in] shared_ptr to RouDiFuzz such that processMessage can be invoked + /// @param[in] std::string containing a message which shall be sent to the interface void fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage); - /// @brief fuzzingRouDiUDS is a method to test the Unix Domain Socket interface of RouDI. It connects to - /// RouDi's uds and sends the message - /// given as input to RouDi - /// @param [in] std::string containing a message which shall be sent to the interface - /// @param [out] int as result of connect(). If the int = -1 it means that it was not possible to connect to the + /// @brief fuzzingRouDiUDS is a method to test the Unix Domain Socket interface of RouDI. It connects to + /// RouDi's uds and sends the message given as input to RouDi + /// @param[in] std::string containing a message which shall be sent to the interface + /// @param[out] int as result of connect(). If the int = -1 it means that it was not possible to connect to the /// socket int fuzzingRouDiUDS(std::string aMessage); - /// @brief fuzzingTOMLParser is a method to send a message to the TOML parser. - /// @param [in] std::string containing a message which shall be sent to the interface - /// @param [in] std::string containing a valid path to an empty file which will be used to write the tomlFile - /// into. This is necessary - /// because the TOML parser expects a path to a file as input + /// @brief fuzzingTOMLParser is a method to send a message to the TOML parser. + /// @param[in] std::string containing a message which shall be sent to the interface + /// @param[in] std::string containing a valid path to an empty file which will be used to write the tomlFile into. + /// This is necessary because the TOML parser expects a path to a file as input void fuzzingTOMLParser(std::string tomlFile, std::string tempFile); }; #endif /* FUZZING_HPP */ diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index c5c658898cb..89ddb7e255c 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -26,20 +26,18 @@ #include #include -unsigned const char TIMEOUT = 50; // 5s for a Timeout -#define INTERFACE_NAME "/test" - int main(int argc, char* argv[]) { + constexpr unsigned char TIMEOUT = 50; // 5s for a Timeout CmdLineParserFuzzing cmd; std::vector allMessages = cmd.parseCmd(argc, argv); - if (cmd.getHelpFlag() == true) + if (cmd.getHelpFlag()) { return 1; } - if (cmd.getErrorFlag() == true) + if (cmd.getErrorFlag()) { std::cout << "No or wrong command lines were specified. Please use --help!" << std::endl; return -1; diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp index bba83ffdcc0..8e63cc3962a 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp @@ -16,14 +16,14 @@ #ifndef FUZZTESTSROUDIWRAPPER_HPP #define FUZZTESTSROUDIWRAPPER_HPP -/// @brief fuzztests_roudi_wrapper contains the main method for the RouDi Wrappers which can be used to fuzz several +/// @brief fuzztests_roudi_wrapper contains the main method for the RouDi Wrappers which can be used to fuzz several /// interfaces /// @brief Main function of the Fuzz Wrapper -/// @param[in] amount of arguments given to the method -/// @param[in] containing the command line parameters -/// @param[out] int containing the status if the fuzzing was successfull. If the return value is -1 something went +/// @param[in] amount of arguments given to the method +/// @param[in] containing the command line parameters +/// @param[out] int containing the status if the fuzzing was successfull. If the return value is -1 something went /// wrong. int main(int argc, char* argv[]); -#endif /*FUZZTESTSROUDIWRAPPE*/ +#endif /*FUZZTESTSROUDIWRAPPER*/ diff --git a/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp b/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp index e7cba86738c..2ca5277ddb8 100644 --- a/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp +++ b/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp @@ -20,8 +20,7 @@ #include "iceoryx_posh/internal/roudi/roudi.hpp" /// @brief RouDiFuzz is a class which inherits from iox::roudi::RouDi to make some protected methods avaialble for -/// Fuzzing. This is necessary, to directly injects messages -/// in these messages to test the robustness of the interfaces +/// Fuzzing. This is necessary, to directly injects messages in these messages to test the robustness of the interfaces class RouDiFuzz : iox::roudi::RouDi { @@ -33,8 +32,8 @@ class RouDiFuzz : iox::roudi::RouDi iox::roudi::RouDi::RuntimeMessagesThreadStart::IMMEDIATE, iox::version::CompatibilityCheckLevel::OFF}); - /// @brief [in] Send a message to the processMessage method of RouDi - /// @param [in] Message which should be sent to the processMessage method of RouDi + /// @brief Send a message to the processMessage method of RouDi + /// @param[in] Message which should be sent to the processMessage method of RouDi void processMessageFuzz(std::string aMessage); }; #endif /*ROUDIFUZZ_HPP*/ diff --git a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp index 76006b13a12..fbe35218e10 100644 --- a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp +++ b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp @@ -18,8 +18,8 @@ #define STRINGTOIPCMESSAGE_HPP /// @brief The StringToIPCMessage is a class which inherits from iox::runtime::IpcInterfaceBase to make the protected -/// method -/// iox::runtime::IpcInterfaceBase::setMessageFromString public and accessible for the fuzz test. +/// method iox::runtime::IpcInterfaceBase::setMessageFromString public and accessible for the fuzz test. + class StringToIPCMessage : public iox::runtime::IpcInterfaceBase { public: From 07f79ad3268e4f8b91f724fe2148b970739c4a0c Mon Sep 17 00:00:00 2001 From: VictorLee Date: Mon, 12 Apr 2021 20:37:35 +0800 Subject: [PATCH 011/127] iox-709 remove duplicate cmake option. Signed-off-by: VictorLee --- iceoryx_meta/build_options.cmake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/iceoryx_meta/build_options.cmake b/iceoryx_meta/build_options.cmake index ad2011302b7..3ace604bd73 100644 --- a/iceoryx_meta/build_options.cmake +++ b/iceoryx_meta/build_options.cmake @@ -39,14 +39,13 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # "Create compile_commands.json file" if(BUILD_ALL) set(EXAMPLES ON) - set(INTROSPECTION ON) set(BUILD_TEST ON) set(INTROSPECTION ON) set(BINDING_C ON) set(DDS_GATEWAY ON) endif() -## must be before the BUILD_TEST check +## must be before the BUILD_TEST check if(COVERAGE AND NOT BUILD_TEST) set(BUILD_TEST ON) set(BUILD_TEST_HINT "${BUILD_TEST_HINT} (activated by COVERAGE=ON)") From 56ed5ffcb8beebc637cefde2e9ea97675d2ad85c Mon Sep 17 00:00:00 2001 From: "Hintz Martin (XC-AD/ESB5)" Date: Tue, 13 Apr 2021 14:35:33 +0200 Subject: [PATCH 012/127] iox-#558 Fix latest merge master errors Signed-off-by: Hintz Martin (XC-AD/ESB5) --- iceoryx_posh/test/fuzztests/fuzz_helper.cpp | 2 +- iceoryx_posh/test/fuzztests/roudi_fuzz.cpp | 2 +- iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/fuzz_helper.cpp b/iceoryx_posh/test/fuzztests/fuzz_helper.cpp index a306b15104b..11f385d2c35 100644 --- a/iceoryx_posh/test/fuzztests/fuzz_helper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzz_helper.cpp @@ -34,7 +34,7 @@ std::shared_ptr FuzzHelper::startRouDiThread() static iox::roudi::IceOryxRouDiComponents m_rouDiComponents(iox::RouDiConfig_t().setDefaults()); static iox::RouDiConfig_t m_config = iox::RouDiConfig_t().setDefaults(); std::shared_ptr aRouDi( - new RouDiFuzz(m_rouDiComponents.m_rouDiMemoryManager, m_rouDiComponents.m_portManager)); + new RouDiFuzz(m_rouDiComponents.rouDiMemoryManager, m_rouDiComponents.portManager)); return aRouDi; } diff --git a/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp b/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp index d492616e6b0..f6be661bce8 100644 --- a/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp +++ b/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp @@ -29,5 +29,5 @@ void RouDiFuzz::processMessageFuzz(std::string aMessage) StringToIPCMessage::setMessageFromString(aMessage.c_str(), ipcMessage); iox::runtime::IpcMessageType cmd = iox::runtime::stringToIpcMessageType(ipcMessage.getElementAtIndex(0).c_str()); std::string processName = ipcMessage.getElementAtIndex(1); - iox::roudi::RouDi::processMessage(ipcMessage, cmd, iox::ProcessName_t(iox::cxx::TruncateToCapacity, processName)); + iox::roudi::RouDi::processMessage(ipcMessage, cmd, iox::RuntimeName_t(iox::cxx::TruncateToCapacity, processName)); } diff --git a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp index fbe35218e10..ea0993d3bb1 100644 --- a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp +++ b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp @@ -23,7 +23,7 @@ class StringToIPCMessage : public iox::runtime::IpcInterfaceBase { public: - StringToIPCMessage(const iox::ProcessName_t& name, const int64_t maxMessages, const int64_t messageSize); + StringToIPCMessage(const iox::RuntimeName_t& name, const int64_t maxMessages, const int64_t messageSize); /// @brief Set the content of answer from buffer. /// @param[in] buffer Raw message as char pointer From abc0ea9e7ea8dfbc0c1b2cb530487415d9fe6b6b Mon Sep 17 00:00:00 2001 From: "Hintz Martin (XC-AD/ESB5)" Date: Tue, 13 Apr 2021 14:56:34 +0200 Subject: [PATCH 013/127] iox-#558 Reorganize help ouput Signed-off-by: Hintz Martin (XC-AD/ESB5) --- .../test/fuzztests/cmdlineparserfuzzing.cpp | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp index ab75b420bcc..da8b30fb858 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp @@ -91,39 +91,36 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) { std::cout << "Usage: " << argv[0] << " [options]" << std::endl; std::cout << "Options:" << std::endl; - std::cout << "-h, --help Display help." << std::endl; - std::cout << "-f, --fuzzing-api Specify API which will be fuzzed." << std::endl; - std::cout << " {uds, com, toml}" << std::endl; - std::cout - << " uds: Starts RouDi and sends messages via Unix Domain " - "Sockets. Multiple messages can be sent. (e.g.: register message first and then offer service)." - << std::endl; - std::cout << " com: Invokes the processMessage method in RouDi " - "directly. This abstracts the IPC and is faster but multiple messages are not supported." + std::cout << "-h, --help" << std::endl; + std::cout << "\tDisplay help." << std::endl; + std::cout << "-f, --fuzzing-api " << std::endl; + std::cout << "\tSpecify API which will be fuzzed." << std::endl; + std::cout << "\t {uds, com, toml}" << std::endl; + std::cout << "\t\tuds: Starts RouDi and sends messages via Unix Domain Sockets. Multiple messages can be " + "sent. (e.g.: register message first and then offer service)." << std::endl; - std::cout - << " toml: Send inputs to test the TOML config file parser. A " - "file is created in your current working directory and the path is sent to the Parser." - << std::endl; - std::cout << "-m, --input-mode {stdin, cl}" << std::endl; - std::cout << " stdin: Send input via stdin." << std::endl; - std::cout << " cl: Send input via commandline. Needs parameter i " - "to send the input." + std::cout << "\t\tcom: Invokes the processMessage method in RouDi directly. This abstracts the IPC and is " + "faster but multiple messages are not supported." << std::endl; - std::cout << "-c, --command-line-file : Read the specified file and send " - "the input to the interface." + std::cout << "\t\ttoml: Send inputs to test the TOML config file parser. A file is created in your current " + "working directory and the path is sent to the parser." << std::endl; - std::cout - << "-i, --command-line-input : Send the input via this command line, requires " - "to use input-mode cl. It's possible to send several commands with several -i commands." - << std::endl; - std::cout - << "-t, --toml-file : Needs to be used when TOML is parsed. The " - "file is used to write messages which will be parsed by the TOML configuration parser." - << std::endl; - std::cout << "-l, --log-level {off, fatal, debug} : Set the log level. " - "Off is standard;" + std::cout << "-m, --input-mode " << std::endl; + std::cout << "\t {stdin, cl}" << std::endl; + std::cout << "\t\tstdin: Send input via stdin." << std::endl; + std::cout << "\t\tcl: Send input via command line. Needs parameter i to send the input." << std::endl; + std::cout << "-c, --command-line-file " << std::endl; + std::cout << "\t Read the specified file and send the input to the interface." << std::endl; + std::cout << "-i, --command-line-input " << std::endl; + std::cout << "\t Send the input via this command line, requires to use input-mode cl. It's possible " + "to send several commands with several -i commands." << std::endl; + std::cout << "-t, --toml-file " << std::endl; + std::cout << "\t Needs to be used when TOML is parsed. The file is used to write messages " + "which will be parsed by the TOML configuration parser." + << std::endl; + std::cout << "-l, --log-level" << std::endl; + std::cout << "\t {off, fatal, debug} : Set the log level. Off is default;" << std::endl; m_helpFlag = true; } break; From ea678942b6cd603f15c554d0d79543a3c84454ad Mon Sep 17 00:00:00 2001 From: "Hintz Martin (XC-AD/ESB5)" Date: Tue, 13 Apr 2021 15:02:03 +0200 Subject: [PATCH 014/127] iox-#558 Typos Signed-off-by: Hintz Martin (XC-AD/ESB5) --- iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp | 4 ++-- iceoryx_posh/test/fuzztests/fuzzing.cpp | 2 +- iceoryx_posh/test/fuzztests/fuzzing.hpp | 4 ++-- iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp | 2 +- iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp | 3 +-- iceoryx_posh/test/fuzztests/roudi_fuzz.hpp | 3 +-- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp index bf66e4d6411..2d25e698e36 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp @@ -42,7 +42,7 @@ class CmdLineParserFuzzing public: CmdLineParserFuzzing(); - /// @brief Parses the command line parameters whiich are entered by starting the fuzz wrappers + /// @brief Parses the command line parameters which are entered by starting the fuzz wrappers /// @param[in] amount of arguments given to the method /// @param[in] containing the command line parameters /// @param[out] Containing the messages which shall be sent to the interface @@ -53,7 +53,7 @@ class CmdLineParserFuzzing bool getHelpFlag(); /// @brief Getter to return m inputMode - /// @param[out] Containing enum InputMode to show if messages are sent to the APi via stdin or command line (cl). + /// @param[out] Containing enum InputMode to show if messages are sent to the API via stdin or command line (cl). InputMode getInputMode(); /// @brief Getter to return m_errorFlag diff --git a/iceoryx_posh/test/fuzztests/fuzzing.cpp b/iceoryx_posh/test/fuzztests/fuzzing.cpp index 4c78315f489..99ebc63285f 100644 --- a/iceoryx_posh/test/fuzztests/fuzzing.cpp +++ b/iceoryx_posh/test/fuzztests/fuzzing.cpp @@ -35,7 +35,7 @@ void Fuzzing::fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMe else { iox::LogDebug() - << "Error, the Smart Pointer for RouDI which is used to call the method 'processMessage' is NULL"; + << "Error, the Smart Pointer for RouDi which is used to call the method 'processMessage' is NULL"; } } diff --git a/iceoryx_posh/test/fuzztests/fuzzing.hpp b/iceoryx_posh/test/fuzztests/fuzzing.hpp index e0f81cd685e..9ce27331124 100644 --- a/iceoryx_posh/test/fuzztests/fuzzing.hpp +++ b/iceoryx_posh/test/fuzztests/fuzzing.hpp @@ -27,7 +27,7 @@ class Fuzzing public: Fuzzing(); - /// @brief fuzzingRouDiCom is a method to test the processMessage method of RouDI. It shall be independent from + /// @brief fuzzingRouDiCom is a method to test the processMessage method of RouDi. It shall be independent from /// the underlying protocol such as uds. It should also be slightly faster since some functions are not invoked /// compared to uds fuzzing. However, a RouDi thread is also started with this approach because otherwise it was not /// be possible to invoke the processMessage method within RouDi without directly modifying the code in RouDi. @@ -35,7 +35,7 @@ class Fuzzing /// @param[in] std::string containing a message which shall be sent to the interface void fuzzingRouDiCom(std::shared_ptr aRouDi, std::string aMessage); - /// @brief fuzzingRouDiUDS is a method to test the Unix Domain Socket interface of RouDI. It connects to + /// @brief fuzzingRouDiUDS is a method to test the Unix Domain Socket interface of RouDi. It connects to /// RouDi's uds and sends the message given as input to RouDi /// @param[in] std::string containing a message which shall be sent to the interface /// @param[out] int as result of connect(). If the int = -1 it means that it was not possible to connect to the diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index 89ddb7e255c..e6e3327bae7 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -90,7 +90,7 @@ int main(int argc, char* argv[]) { if (timeout >= TIMEOUT) { - std::cout << "RouDI could not be started, program terminates!" << std::endl; + std::cout << "RouDi could not be started, program terminates!" << std::endl; return -1; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 1/10 of a second diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp index 8e63cc3962a..4b66ffb8d6c 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp @@ -22,8 +22,7 @@ /// @brief Main function of the Fuzz Wrapper /// @param[in] amount of arguments given to the method /// @param[in] containing the command line parameters -/// @param[out] int containing the status if the fuzzing was successfull. If the return value is -1 something went -/// wrong. +/// @param[out] int containing the status if the fuzzing was successful. If the return value is -1 something went wrong. int main(int argc, char* argv[]); #endif /*FUZZTESTSROUDIWRAPPER*/ diff --git a/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp b/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp index 2ca5277ddb8..acbda8e40a0 100644 --- a/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp +++ b/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp @@ -19,9 +19,8 @@ #include "iceoryx_posh/internal/roudi/roudi.hpp" -/// @brief RouDiFuzz is a class which inherits from iox::roudi::RouDi to make some protected methods avaialble for +/// @brief RouDiFuzz is a class which inherits from iox::roudi::RouDi to make some protected methods available for /// Fuzzing. This is necessary, to directly injects messages in these messages to test the robustness of the interfaces - class RouDiFuzz : iox::roudi::RouDi { public: From 03295750a3a37acc61b48754427ec0f2a6cddcfc Mon Sep 17 00:00:00 2001 From: "Hintz Martin (XC-AD/ESB5)" Date: Tue, 13 Apr 2021 15:05:29 +0200 Subject: [PATCH 015/127] iox-#558 Remove tabs Signed-off-by: Hintz Martin (XC-AD/ESB5) --- .../test/fuzztests/cmdlineparserfuzzing.hpp | 38 +++++++++---------- iceoryx_posh/test/fuzztests/fuzz_helper.hpp | 22 +++++------ .../fuzztests/fuzztests_roudi_wrapper.hpp | 6 +-- iceoryx_posh/test/fuzztests/roudi_fuzz.hpp | 2 +- .../test/fuzztests/string_to_ipc_message.hpp | 2 +- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp index 2d25e698e36..7238685c611 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp @@ -36,44 +36,44 @@ enum class InputMode }; /// @brief CmdLineParserFuzzing is a class which parses the command lines to configure the Fuzz Wrappers for -/// example to tell which interface shall be fuzzed. +/// example to tell which interface shall be fuzzed. class CmdLineParserFuzzing { public: CmdLineParserFuzzing(); - /// @brief Parses the command line parameters which are entered by starting the fuzz wrappers - /// @param[in] amount of arguments given to the method - /// @param[in] containing the command line parameters - /// @param[out] Containing the messages which shall be sent to the interface + /// @brief Parses the command line parameters which are entered by starting the fuzz wrappers + /// @param[in] amount of arguments given to the method + /// @param[in] containing the command line parameters + /// @param[out] Containing the messages which shall be sent to the interface std::vector parseCmd(int argc, char* argv[]) noexcept; - /// @brief Getter to return m_helpFlag - /// @param[out] Containing a flag showing if the help menu was displayed. + /// @brief Getter to return m_helpFlag + /// @param[out] Containing a flag showing if the help menu was displayed. bool getHelpFlag(); - /// @brief Getter to return m inputMode - /// @param[out] Containing enum InputMode to show if messages are sent to the API via stdin or command line (cl). + /// @brief Getter to return m inputMode + /// @param[out] Containing enum InputMode to show if messages are sent to the API via stdin or command line (cl). InputMode getInputMode(); - /// @brief Getter to return m_errorFlag - /// @param[out] Containing a flag showing if an error happened and fuzzing cannot be started + /// @brief Getter to return m_errorFlag + /// @param[out] Containing a flag showing if an error happened and fuzzing cannot be started bool getErrorFlag(); - /// @brief Getter to return m_cmdLineFlag - /// @param[out] Containing a flag showing if a command line parameter was given after InputMode::CL was set + /// @brief Getter to return m_cmdLineFlag + /// @param[out] Containing a flag showing if a command line parameter was given after InputMode::CL was set bool getCmdLineFlag(); - /// @brief Getter to return m_fuzzingAPI - /// @param[out] Containing enum FuzzingFlag indicating which API wants to be fuzzed. + /// @brief Getter to return m_fuzzingAPI + /// @param[out] Containing enum FuzzingFlag indicating which API wants to be fuzzed. FuzzingApi getFuzzingAPI(); - /// @brief Getter to return m_tomlFileFlag - /// @param[out] Containing a flag showing if TOML API wants to be fuzzed. + /// @brief Getter to return m_tomlFileFlag + /// @param[out] Containing a flag showing if TOML API wants to be fuzzed. bool getTomlFileFlag(); - /// @brief Getter to return m_tomlFile - /// @param[out] Containing an std::string to a file which can be used to temporarily write a TOML configuration to + /// @brief Getter to return m_tomlFile + /// @param[out] Containing an std::string to a file which can be used to temporarily write a TOML configuration to /// the file. std::string getTomlFile(); diff --git a/iceoryx_posh/test/fuzztests/fuzz_helper.hpp b/iceoryx_posh/test/fuzztests/fuzz_helper.hpp index e5d29d14cc7..9eca2b3e4d5 100644 --- a/iceoryx_posh/test/fuzztests/fuzz_helper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzz_helper.hpp @@ -24,28 +24,28 @@ class FuzzHelper { public: - /// @brief Reads messages from stdin and writes them into a std::vector - /// @param[in] message via stdin - /// @param[out] std::vector containing std::strings of the messages from stdin. Each std::string in the vector + /// @brief Reads messages from stdin and writes them into a std::vector + /// @param[in] message via stdin + /// @param[out] std::vector containing std::strings of the messages from stdin. Each std::string in the vector /// is one line of stdin. This means that if there is one newline in stdin, there will be two std::strings, with two /// newlines, there will be three messages,... std::vector getStdInMessages(); - /// @brief a shared Ptr to a RouDi thread which will be used to keep the thread alive until the message is + /// @brief a shared Ptr to a RouDi thread which will be used to keep the thread alive until the message is /// processed by RouDi - /// @param[out] a shated_ptr to RouDiFuzz which inherits from RouDi + /// @param[out] a shated_ptr to RouDiFuzz which inherits from RouDi std::shared_ptr startRouDiThread(); - /// @brief Splitted messages in allMessages are put together as one String. This is used for TOML parser for + /// @brief Splitted messages in allMessages are put together as one String. This is used for TOML parser for /// example because one message can contain newlines - /// @param[in] std::vector containing several std::string messages which shall be sent to an interface - /// @param[out] std::vector containing one std::string message + /// @param[in] std::vector containing several std::string messages which shall be sent to an interface + /// @param[out] std::vector containing one std::string message std::vector combineString(std::vector allMessages); - /// @brief A method to check if RouDi is alive. It checks if the UDS is available and then sends a default message + /// @brief A method to check if RouDi is alive. It checks if the UDS is available and then sends a default message /// to RouDi - /// @param[out] Boolean value indicating if RouDi is available + /// @param[out] Boolean value indicating if RouDi is available bool checkIsRouDiRunning(); }; -#endif /*FUZZHELPER_HPP*/ \ No newline at end of file +#endif /*FUZZHELPER_HPP*/ diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp index 4b66ffb8d6c..4ad40ac37d3 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.hpp @@ -19,10 +19,10 @@ /// @brief fuzztests_roudi_wrapper contains the main method for the RouDi Wrappers which can be used to fuzz several /// interfaces -/// @brief Main function of the Fuzz Wrapper +/// @brief Main function of the Fuzz Wrapper /// @param[in] amount of arguments given to the method -/// @param[in] containing the command line parameters -/// @param[out] int containing the status if the fuzzing was successful. If the return value is -1 something went wrong. +/// @param[in] containing the command line parameters +/// @param[out] int containing the status if the fuzzing was successful. If the return value is -1 something went wrong. int main(int argc, char* argv[]); #endif /*FUZZTESTSROUDIWRAPPER*/ diff --git a/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp b/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp index acbda8e40a0..5b91fe1aafd 100644 --- a/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp +++ b/iceoryx_posh/test/fuzztests/roudi_fuzz.hpp @@ -19,7 +19,7 @@ #include "iceoryx_posh/internal/roudi/roudi.hpp" -/// @brief RouDiFuzz is a class which inherits from iox::roudi::RouDi to make some protected methods available for +/// @brief RouDiFuzz is a class which inherits from iox::roudi::RouDi to make some protected methods available for /// Fuzzing. This is necessary, to directly injects messages in these messages to test the robustness of the interfaces class RouDiFuzz : iox::roudi::RouDi { diff --git a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp index ea0993d3bb1..94a9d8f102b 100644 --- a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp +++ b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp @@ -17,7 +17,7 @@ #ifndef STRINGTOIPCMESSAGE_HPP #define STRINGTOIPCMESSAGE_HPP -/// @brief The StringToIPCMessage is a class which inherits from iox::runtime::IpcInterfaceBase to make the protected +/// @brief The StringToIPCMessage is a class which inherits from iox::runtime::IpcInterfaceBase to make the protected /// method iox::runtime::IpcInterfaceBase::setMessageFromString public and accessible for the fuzz test. class StringToIPCMessage : public iox::runtime::IpcInterfaceBase From 4f204bdd416d4c0a540903b48e115b314c131bb1 Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Mon, 12 Apr 2021 15:31:01 +0200 Subject: [PATCH 016/127] iox-#14 add member to ChunkHeader, reduce its alignment requirents and adjust code accordingly Signed-off-by: Mathias Kraus --- doc/design/chunk_header.md | 17 +++++---- .../internal/mepoo/chunk_management.hpp | 2 +- .../iceoryx_posh/internal/mepoo/mem_pool.hpp | 5 ++- .../internal/mepoo/memory_manager.hpp | 2 +- .../internal/mepoo/typed_mem_pool.inl | 4 +- .../iceoryx_posh/mepoo/chunk_header.hpp | 6 +-- iceoryx_posh/source/mepoo/chunk_header.cpp | 6 +++ iceoryx_posh/source/mepoo/mem_pool.cpp | 9 ++--- iceoryx_posh/source/mepoo/memory_manager.cpp | 2 +- .../mempool_collection_memory_block.cpp | 4 +- .../moduletests/test_mepoo_chunk_header.cpp | 4 +- .../moduletests/test_mepoo_chunk_settings.cpp | 4 +- .../moduletests/test_popo_chunk_receiver.cpp | 13 ++----- .../moduletests/test_popo_chunk_sender.cpp | 37 ++++--------------- .../error_handling/error_handling.hpp | 2 +- 15 files changed, 48 insertions(+), 69 deletions(-) diff --git a/doc/design/chunk_header.md b/doc/design/chunk_header.md index ff784fad4ea..98985b77ee6 100644 --- a/doc/design/chunk_header.md +++ b/doc/design/chunk_header.md @@ -35,33 +35,34 @@ Framing with terminology - iceoryx runs on multiple platforms -> endianness of recorded chunks might differ - for tracing, a chunk should be uniquely identifiable -> store origin and sequence number - the chunk is located in the shared memory, which will be mapped to arbitrary positions in the address space of various processes -> no absolute pointer are allowed -- aligning the `ChunkHeader` to 32 bytes will ensure that all member are on the same cache line and will improve performance - in order to reduce complexity, the alignment of the user-header must not exceed the alignment of the `ChunkHeader` ### Solution #### ChunkHeader Definition ``` -struct alignas(32) ChunkHeader +class ChunkHeader { uint32_t chunkSize; uint8_t chunkHeaderVersion; - uint8_t reserved1; - uint8_t reserved2; - uint8_t reserved3; + uint8_t reserved[3]; uint64_t originId; uint64_t sequenceNumber; - uint32_t userPayloadSize; + uint32_t userHeaderSize{0U}; + uint32_t userPayloadSize{0U}; + uint32_t userPayloadAlignment{1U}; uint32_t userPayloadOffset; }; ``` - **chunkSize** is the size of the whole chunk - **chunkHeaderVersion** is used to detect incompatibilities for record&replay functionality -- **reserved1**, **reserved2**, **reserved3** are currently not used and set to `0` +- **reserved[3]** is currently not used and set to `0` - **originId** is the unique identifier of the publisher the chunk was sent from - **sequenceNumber** is a serial number for the sent chunks +- **userPayloadSize** is the size of the chunk occupied by the user-header - **userPayloadSize** is the size of the chunk occupied by the user-payload +- **userPayloadAlignment** is the alignment of the chunk occupied by the user-payload - **userPayloadOffset** is the offset of the user-payload relative to the begin of the chunk #### Framing @@ -160,7 +161,7 @@ chunkSize = sizeof(chunkHeader) + userPayloadSize; 2. No user-header and user-payload alignment exceeds the `ChunkHeader` alignment -Worst case scenario is when a part of the `ChunkHeader` crosses the user-payload alignment boundary, so that the user-payload must be aligned to the next boundary. Currently this is not possible, since the size equals the alignment of the `ChunkHeader`. This might change if more member are added to the `ChunkHeader` or the alignment is reduced. The following drawing demonstrates this scenario. +Worst case scenario is when a part of the `ChunkHeader` crosses the user-payload alignment boundary, so that the user-payload must be aligned to the next boundary. The following drawing demonstrates this scenario. ``` ┌ back-offset diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/chunk_management.hpp b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/chunk_management.hpp index aef1a34794a..2cfd78634f0 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/chunk_management.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/chunk_management.hpp @@ -31,7 +31,7 @@ namespace mepoo class MemPool; struct ChunkHeader; -struct alignas(32) ChunkManagement +struct ChunkManagement { using base_t = ChunkHeader; using referenceCounterBase_t = uint64_t; diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/mem_pool.hpp b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/mem_pool.hpp index 7e4431d2f19..ecb5f0c4ed2 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/mem_pool.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/mem_pool.hpp @@ -17,6 +17,7 @@ #ifndef IOX_POSH_MEPOO_MEM_POOL_HPP #define IOX_POSH_MEPOO_MEM_POOL_HPP +#include "iceoryx_posh/mepoo/chunk_header.hpp" #include "iceoryx_utils/cxx/helplets.hpp" #include "iceoryx_utils/internal/concurrent/loffli.hpp" #include "iceoryx_utils/internal/posix_wrapper/shared_memory_object/allocator.hpp" @@ -47,9 +48,9 @@ class MemPool { public: using freeList_t = concurrent::LoFFLi; - static constexpr uint64_t MEMORY_ALIGNMENT = posix::Allocator::MEMORY_ALIGNMENT; + static constexpr uint64_t CHUNK_MEMORY_ALIGNMENT = 8U; // default alignment for 64 bit - MemPool(const cxx::greater_or_equal chunkSize, + MemPool(const cxx::greater_or_equal chunkSize, const cxx::greater_or_equal numberOfChunks, posix::Allocator& managementAllocator, posix::Allocator& chunkMemoryAllocator) noexcept; diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/memory_manager.hpp b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/memory_manager.hpp index 2f73c467b91..ae425da0453 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/memory_manager.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/memory_manager.hpp @@ -73,7 +73,7 @@ class MemoryManager void printMemPoolVector(log::LogStream& log) const noexcept; void addMemPool(posix::Allocator& managementAllocator, posix::Allocator& chunkMemoryAllocator, - const cxx::greater_or_equal chunkPayloadSize, + const cxx::greater_or_equal chunkPayloadSize, const cxx::greater_or_equal numberOfChunks) noexcept; void generateChunkManagementPool(posix::Allocator& managementAllocator) noexcept; diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/typed_mem_pool.inl b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/typed_mem_pool.inl index ea7c7f91951..5f66bee0f60 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/typed_mem_pool.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/typed_mem_pool.inl @@ -130,9 +130,7 @@ inline uint64_t TypedMemPool::requiredChunkSize() noexcept // this is safe since we use correct values for size and alignment auto& chunkSettings = chunkSettingsResult.value(); - return cxx::align( - std::max(static_cast(chunkSettings.requiredChunkSize()), posix::Allocator::MEMORY_ALIGNMENT), - MemPool::MEMORY_ALIGNMENT); + return cxx::align(static_cast(chunkSettings.requiredChunkSize()), MemPool::CHUNK_MEMORY_ALIGNMENT); } template diff --git a/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp b/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp index ebb7f3c66ed..20bd4d2d943 100644 --- a/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp +++ b/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp @@ -38,9 +38,7 @@ struct NoUserHeader { }; -/// @brief IMPORTANT the alignment MUST be 32 or less since all mempools are -/// 32 byte aligned otherwise we get alignment problems! -struct alignas(32) ChunkHeader +struct ChunkHeader { using UserPayloadOffset_t = uint32_t; @@ -136,7 +134,9 @@ struct alignas(32) ChunkHeader uint8_t m_reserved[3]{}; UniquePortId m_originId{popo::InvalidId}; uint64_t m_sequenceNumber{0U}; + uint32_t m_userHeaderSize{0U}; uint32_t m_userPayloadSize{0U}; + uint32_t m_userPayloadAlignment{1U}; UserPayloadOffset_t m_userPayloadOffset{sizeof(ChunkHeader)}; }; diff --git a/iceoryx_posh/source/mepoo/chunk_header.cpp b/iceoryx_posh/source/mepoo/chunk_header.cpp index a2d5e03c20b..26fcf948d76 100644 --- a/iceoryx_posh/source/mepoo/chunk_header.cpp +++ b/iceoryx_posh/source/mepoo/chunk_header.cpp @@ -16,6 +16,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "iceoryx_posh/mepoo/chunk_header.hpp" +#include "iceoryx_posh/internal/mepoo/mem_pool.hpp" #include "iceoryx_utils/cxx/helplets.hpp" namespace iox @@ -24,10 +25,15 @@ namespace mepoo { ChunkHeader::ChunkHeader(const uint32_t chunkSize, const ChunkSettings& chunkSettings) noexcept : m_chunkSize(chunkSize) + , m_userHeaderSize(chunkSettings.userHeaderSize()) , m_userPayloadSize(chunkSettings.userPayloadSize()) + , m_userPayloadAlignment(chunkSettings.userPayloadAlignment()) { static_assert(alignof(ChunkHeader) >= 8U, "All the calculations expect the ChunkHeader alignment to be at least 8!"); + static_assert(alignof(ChunkHeader) <= mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT, + "The ChunkHeader must not exceed the alignment of the mempool chunks, which are aligned to " + "'MemPool::CHUNK_MEMORY_ALIGNMENT'!"); const auto userPayloadAlignment = chunkSettings.userPayloadAlignment(); const auto userHeaderSize = chunkSettings.userHeaderSize(); diff --git a/iceoryx_posh/source/mepoo/mem_pool.cpp b/iceoryx_posh/source/mepoo/mem_pool.cpp index 278f3ffee0f..d968feabc9b 100644 --- a/iceoryx_posh/source/mepoo/mem_pool.cpp +++ b/iceoryx_posh/source/mepoo/mem_pool.cpp @@ -38,7 +38,7 @@ MemPoolInfo::MemPoolInfo(const uint32_t usedChunks, { } -MemPool::MemPool(const cxx::greater_or_equal chunkSize, +MemPool::MemPool(const cxx::greater_or_equal chunkSize, const cxx::greater_or_equal numberOfChunks, posix::Allocator& managementAllocator, posix::Allocator& chunkMemoryAllocator) noexcept @@ -49,7 +49,7 @@ MemPool::MemPool(const cxx::greater_or_equal chunkSi if (isMultipleOfAlignment(chunkSize)) { m_rawMemory = - static_cast(chunkMemoryAllocator.allocate(static_cast(m_numberOfChunks) * m_chunkSize)); + static_cast(chunkMemoryAllocator.allocate(static_cast(m_numberOfChunks) * m_chunkSize, CHUNK_MEMORY_ALIGNMENT)); auto memoryLoFFLi = static_cast(managementAllocator.allocate(freeList_t::requiredMemorySize(m_numberOfChunks))); m_freeIndices.init(memoryLoFFLi, m_numberOfChunks); @@ -57,14 +57,13 @@ MemPool::MemPool(const cxx::greater_or_equal chunkSi else { std::cerr << chunkSize << " :: " << numberOfChunks << std::endl; - errorHandler( - Error::kMEPOO__MEMPOOL_CHUNKSIZE_MUST_BE_LARGER_THAN_SHARED_MEMORY_ALIGNMENT_AND_MULTIPLE_OF_ALIGNMENT); + errorHandler(Error::kMEPOO__MEMPOOL_CHUNKSIZE_MUST_BE_MULTIPLE_OF_CHUNK_MEMORY_ALIGNMENT); } } bool MemPool::isMultipleOfAlignment(const uint32_t value) const noexcept { - return (value % SHARED_MEMORY_ALIGNMENT == 0U); + return (value % CHUNK_MEMORY_ALIGNMENT == 0U); } void MemPool::adjustMinFree() noexcept diff --git a/iceoryx_posh/source/mepoo/memory_manager.cpp b/iceoryx_posh/source/mepoo/memory_manager.cpp index 3bc1b1355fb..69bfdd44675 100644 --- a/iceoryx_posh/source/mepoo/memory_manager.cpp +++ b/iceoryx_posh/source/mepoo/memory_manager.cpp @@ -44,7 +44,7 @@ void MemoryManager::printMemPoolVector(log::LogStream& log) const noexcept void MemoryManager::addMemPool(posix::Allocator& managementAllocator, posix::Allocator& chunkMemoryAllocator, - const cxx::greater_or_equal chunkPayloadSize, + const cxx::greater_or_equal chunkPayloadSize, const cxx::greater_or_equal numberOfChunks) noexcept { uint32_t adjustedChunkSize = sizeWithChunkHeaderStruct(static_cast(chunkPayloadSize)); diff --git a/iceoryx_posh/source/roudi/memory/mempool_collection_memory_block.cpp b/iceoryx_posh/source/roudi/memory/mempool_collection_memory_block.cpp index 35dca019836..550ed77517d 100644 --- a/iceoryx_posh/source/roudi/memory/mempool_collection_memory_block.cpp +++ b/iceoryx_posh/source/roudi/memory/mempool_collection_memory_block.cpp @@ -39,14 +39,14 @@ MemPoolCollectionMemoryBlock::~MemPoolCollectionMemoryBlock() noexcept uint64_t MemPoolCollectionMemoryBlock::size() const noexcept { - return cxx::align(static_cast(sizeof(mepoo::MemoryManager)), mepoo::MemPool::MEMORY_ALIGNMENT) + return cxx::align(static_cast(sizeof(mepoo::MemoryManager)), mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT) + mepoo::MemoryManager::requiredFullMemorySize(m_memPoolConfig); } uint64_t MemPoolCollectionMemoryBlock::alignment() const noexcept { // algorithm::align doesn't like constexpr values - auto memPoolAlignment = mepoo::MemPool::MEMORY_ALIGNMENT; + auto memPoolAlignment = mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT; return algorithm::max(static_cast(alignof(mepoo::MemoryManager)), memPoolAlignment); } diff --git a/iceoryx_posh/test/moduletests/test_mepoo_chunk_header.cpp b/iceoryx_posh/test/moduletests/test_mepoo_chunk_header.cpp index d2906af2f9e..1e8664e3669 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_chunk_header.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_chunk_header.cpp @@ -447,7 +447,9 @@ TEST_P(ChunkHeader_AlteringUserPayloadWithUserHeader, CheckIntegrityOfChunkHeade SCOPED_TRACE(std::string("User-Payload: size = ") + std::to_string(userPayloadParams.size) + std::string("; alignment = ") + std::to_string(userPayloadParams.alignment)); - constexpr uint32_t USER_HEADER_SIZES[]{1U, sizeof(ChunkHeader) / 2U, sizeof(ChunkHeader), sizeof(ChunkHeader) * 2U}; + constexpr uint32_t SMALL_USER_HEADER{alignof(ChunkHeader)}; + static_assert(SMALL_USER_HEADER < sizeof(ChunkHeader), "For this test the size must be smaller than ChunkHeader"); + constexpr uint32_t USER_HEADER_SIZES[]{1U, SMALL_USER_HEADER, sizeof(ChunkHeader), sizeof(ChunkHeader) * 2U}; constexpr uint32_t USER_HEADER_ALIGNMENTS[]{0U, 1U, alignof(ChunkHeader) / 2U, alignof(ChunkHeader)}; for (const auto userHeaderAlignment : USER_HEADER_ALIGNMENTS) diff --git a/iceoryx_posh/test/moduletests/test_mepoo_chunk_settings.cpp b/iceoryx_posh/test/moduletests/test_mepoo_chunk_settings.cpp index 36a0f6c69f7..ab51e5cced4 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_chunk_settings.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_chunk_settings.cpp @@ -362,7 +362,9 @@ TEST_P(ChunkSettings_AlteringUserPayloadWithUserHeader, SCOPED_TRACE(std::string("User-Payload: size = ") + std::to_string(userPayload.size) + std::string("; alignment = ") + std::to_string(userPayload.alignment)); - constexpr uint32_t USER_HEADER_SIZES[]{1U, sizeof(ChunkHeader) / 2U, sizeof(ChunkHeader), sizeof(ChunkHeader) * 2U}; + constexpr uint32_t SMALL_USER_HEADER{alignof(ChunkHeader)}; + static_assert(SMALL_USER_HEADER < sizeof(ChunkHeader), "For this test the size must be smaller than ChunkHeader"); + constexpr uint32_t USER_HEADER_SIZES[]{1U, SMALL_USER_HEADER, sizeof(ChunkHeader), sizeof(ChunkHeader) * 2U}; constexpr uint32_t USER_HEADER_ALIGNMENTS[]{0U, 1U, alignof(ChunkHeader) / 2U, alignof(ChunkHeader)}; for (const auto userHeaderAlignment : USER_HEADER_ALIGNMENTS) diff --git a/iceoryx_posh/test/moduletests/test_popo_chunk_receiver.cpp b/iceoryx_posh/test/moduletests/test_popo_chunk_receiver.cpp index c5609d64ff7..be7268efad9 100644 --- a/iceoryx_posh/test/moduletests/test_popo_chunk_receiver.cpp +++ b/iceoryx_posh/test/moduletests/test_popo_chunk_receiver.cpp @@ -22,6 +22,7 @@ #include "iceoryx_posh/internal/popo/building_blocks/chunk_receiver_data.hpp" #include "iceoryx_posh/internal/popo/building_blocks/locking_policy.hpp" #include "iceoryx_posh/mepoo/mepoo_config.hpp" +#include "iceoryx_posh/testing/mocks/chunk_mock.hpp" #include "iceoryx_utils/error_handling/error_handling.hpp" #include "iceoryx_utils/internal/posix_wrapper/shared_memory_object/allocator.hpp" #include "test.hpp" @@ -198,16 +199,8 @@ TEST_F(ChunkReceiver_test, releaseInvalidChunk) errorHandlerCalled = true; }); - constexpr uint32_t CHUNK_SIZE{32U}; - constexpr uint32_t USER_PAYLOAD_SIZE{0U}; - - auto chunkSettingsResult = - iox::mepoo::ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); - ASSERT_FALSE(chunkSettingsResult.has_error()); - auto& chunkSettings = chunkSettingsResult.value(); - - iox::mepoo::ChunkHeader myCrazyChunk{CHUNK_SIZE, chunkSettings}; - m_chunkReceiver.release(&myCrazyChunk); + ChunkMock myCrazyChunk; + m_chunkReceiver.release(myCrazyChunk.chunkHeader()); EXPECT_TRUE(errorHandlerCalled); EXPECT_THAT(m_memoryManager.getMemPoolInfo(0).m_usedChunks, Eq(1U)); diff --git a/iceoryx_posh/test/moduletests/test_popo_chunk_sender.cpp b/iceoryx_posh/test/moduletests/test_popo_chunk_sender.cpp index 11cd0efae8a..0b4a8c66b48 100644 --- a/iceoryx_posh/test/moduletests/test_popo_chunk_sender.cpp +++ b/iceoryx_posh/test/moduletests/test_popo_chunk_sender.cpp @@ -27,6 +27,7 @@ #include "iceoryx_posh/internal/popo/building_blocks/locking_policy.hpp" #include "iceoryx_posh/internal/popo/ports/base_port.hpp" #include "iceoryx_posh/mepoo/mepoo_config.hpp" +#include "iceoryx_posh/testing/mocks/chunk_mock.hpp" #include "iceoryx_utils/cxx/generic_raii.hpp" #include "iceoryx_utils/error_handling/error_handling.hpp" #include "iceoryx_utils/internal/posix_wrapper/shared_memory_object/allocator.hpp" @@ -235,16 +236,8 @@ TEST_F(ChunkSender_test, freeInvalidChunk) errorHandlerCalled = true; }); - constexpr uint32_t CHUNK_SIZE{32U}; - constexpr uint32_t USER_PAYLOAD_SIZE{0U}; - - auto chunkSettingsResult = - iox::mepoo::ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); - ASSERT_FALSE(chunkSettingsResult.has_error()); - auto& chunkSettings = chunkSettingsResult.value(); - - iox::mepoo::ChunkHeader myCrazyChunk{CHUNK_SIZE, chunkSettings}; - m_chunkSender.release(&myCrazyChunk); + ChunkMock myCrazyChunk; + m_chunkSender.release(myCrazyChunk.chunkHeader()); EXPECT_TRUE(errorHandlerCalled); EXPECT_THAT(m_memoryManager.getMemPoolInfo(0).m_usedChunks, Eq(1U)); @@ -460,16 +453,8 @@ TEST_F(ChunkSender_test, sendInvalidChunk) errorHandlerCalled = true; }); - constexpr uint32_t CHUNK_SIZE{32U}; - constexpr uint32_t USER_PAYLOAD_SIZE{0U}; - - auto chunkSettingsResult = - iox::mepoo::ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); - ASSERT_FALSE(chunkSettingsResult.has_error()); - auto& chunkSettings = chunkSettingsResult.value(); - - iox::mepoo::ChunkHeader myCrazyChunk{CHUNK_SIZE, chunkSettings}; - m_chunkSender.send(&myCrazyChunk); + ChunkMock myCrazyChunk; + m_chunkSender.send(myCrazyChunk.chunkHeader()); EXPECT_TRUE(errorHandlerCalled); EXPECT_THAT(m_memoryManager.getMemPoolInfo(0).m_usedChunks, Eq(1U)); @@ -502,16 +487,8 @@ TEST_F(ChunkSender_test, pushInvalidChunkToHistory) errorHandlerCalled = true; }); - constexpr uint32_t CHUNK_SIZE{32U}; - constexpr uint32_t USER_PAYLOAD_SIZE{0U}; - - auto chunkSettingsResult = - iox::mepoo::ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); - ASSERT_FALSE(chunkSettingsResult.has_error()); - auto& chunkSettings = chunkSettingsResult.value(); - - iox::mepoo::ChunkHeader myCrazyChunk{CHUNK_SIZE, chunkSettings}; - m_chunkSender.pushToHistory(&myCrazyChunk); + ChunkMock myCrazyChunk; + m_chunkSender.pushToHistory(myCrazyChunk.chunkHeader()); EXPECT_TRUE(errorHandlerCalled); EXPECT_THAT(m_memoryManager.getMemPoolInfo(0).m_usedChunks, Eq(1U)); diff --git a/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp b/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp index 53dc5300848..9b73a1cbcc3 100644 --- a/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp +++ b/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp @@ -101,7 +101,7 @@ namespace iox error(MEPOO__MEMPOOL_GETCHUNK_CHUNK_WITHOUT_MEMPOOL) \ error(MEPOO__MEMPOOL_GETCHUNK_CHUNK_IS_TOO_LARGE) \ error(MEPOO__MEMPOOL_GETCHUNK_POOL_IS_RUNNING_OUT_OF_CHUNKS) \ - error(MEPOO__MEMPOOL_CHUNKSIZE_MUST_BE_LARGER_THAN_SHARED_MEMORY_ALIGNMENT_AND_MULTIPLE_OF_ALIGNMENT) \ + error(MEPOO__MEMPOOL_CHUNKSIZE_MUST_BE_MULTIPLE_OF_CHUNK_MEMORY_ALIGNMENT) \ error(MEPOO__MEMPOOL_ADDMEMPOOL_AFTER_GENERATECHUNKMANAGEMENTPOOL) \ error(MEPOO__TYPED_MEMPOOL_HAS_INCONSISTENT_STATE) \ error(MEPOO__TYPED_MEMPOOL_MANAGEMENT_SEGMENT_IS_BROKEN) \ From f082fdd6591b484c27e248b97d4ce0caadd57958 Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Mon, 12 Apr 2021 16:57:19 +0200 Subject: [PATCH 017/127] iox-#14 add methods and tests for userHeaderSize and userPayloadAlignment Signed-off-by: Mathias Kraus --- .../iceoryx_posh/mepoo/chunk_header.hpp | 8 ++++++++ iceoryx_posh/source/mepoo/chunk_header.cpp | 9 +++++++++ .../moduletests/test_mepoo_chunk_header.cpp | 18 +++++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp b/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp index 20bd4d2d943..db0d7924366 100644 --- a/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp +++ b/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp @@ -99,10 +99,18 @@ struct ChunkHeader /// @return the chunk size uint32_t chunkSize() const noexcept; + /// @brief The size of the chunk occupied by the user-header + /// @return the user-header size + uint32_t userHeaderSize() const noexcept; + /// @brief The size of the chunk occupied by the user-payload /// @return the user-payload size uint32_t userPayloadSize() const noexcept; + /// @brief The alignment of the chunk occupied by the user-payload + /// @return the user-payload alignment + uint32_t userPayloadAlignment() const noexcept; + /// @brief The unique identifier of the publisher the chunk was sent from /// @return the id of the publisher the chunk was sent from UniquePortId originId() const; diff --git a/iceoryx_posh/source/mepoo/chunk_header.cpp b/iceoryx_posh/source/mepoo/chunk_header.cpp index 26fcf948d76..ba341ea2f2f 100644 --- a/iceoryx_posh/source/mepoo/chunk_header.cpp +++ b/iceoryx_posh/source/mepoo/chunk_header.cpp @@ -158,11 +158,20 @@ uint32_t ChunkHeader::chunkSize() const noexcept return m_chunkSize; } +uint32_t ChunkHeader::userHeaderSize() const noexcept +{ + return m_userHeaderSize; +} + uint32_t ChunkHeader::userPayloadSize() const noexcept { return m_userPayloadSize; } +uint32_t ChunkHeader::userPayloadAlignment() const noexcept +{ + return m_userPayloadAlignment; +} UniquePortId ChunkHeader::originId() const { diff --git a/iceoryx_posh/test/moduletests/test_mepoo_chunk_header.cpp b/iceoryx_posh/test/moduletests/test_mepoo_chunk_header.cpp index 1e8664e3669..067fcda1eec 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_chunk_header.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_chunk_header.cpp @@ -32,8 +32,9 @@ TEST(ChunkHeader_test, ChunkHeaderHasInitializedMembers) { constexpr uint32_t CHUNK_SIZE{753U}; constexpr uint32_t USER_PAYLOAD_SIZE{8U}; + constexpr uint32_t USER_PAYLOAD_ALIGNMENT{iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT}; - auto chunkSettingsResult = ChunkSettings::create(USER_PAYLOAD_SIZE, iox::CHUNK_DEFAULT_USER_PAYLOAD_ALIGNMENT); + auto chunkSettingsResult = ChunkSettings::create(USER_PAYLOAD_SIZE, USER_PAYLOAD_ALIGNMENT); ASSERT_FALSE(chunkSettingsResult.has_error()); auto& chunkSettings = chunkSettingsResult.value(); @@ -48,7 +49,9 @@ TEST(ChunkHeader_test, ChunkHeaderHasInitializedMembers) EXPECT_THAT(sut.sequenceNumber(), Eq(0U)); + EXPECT_THAT(sut.userHeaderSize(), Eq(0U)); EXPECT_THAT(sut.userPayloadSize(), Eq(USER_PAYLOAD_SIZE)); + EXPECT_THAT(sut.userPayloadAlignment(), Eq(USER_PAYLOAD_ALIGNMENT)); // a default created ChunkHeader has always an adjacent user-payload const uint64_t chunkStartAddress{reinterpret_cast(&sut)}; @@ -288,6 +291,17 @@ void createChunksOnMultipleAddresses(const PayloadParams& userPayloadParams, } } +void checkUserHeaderSizeAndPayloadSizeAndAlignmentIsSet(const ChunkHeader& sut, + const PayloadParams& userPayloadParams, + const uint32_t userHeaderSize) +{ + EXPECT_EQ(sut.userPayloadSize(), userPayloadParams.size); + // a user-payload alignment of zero will internally be set to one + auto adjustedAlignment = userPayloadParams.alignment == 0U ? 1U : userPayloadParams.alignment; + EXPECT_EQ(sut.userPayloadAlignment(), adjustedAlignment); + EXPECT_EQ(sut.userHeaderSize(), userHeaderSize); +} + void checkUserPayloadNotOverlappingWithChunkHeader(const ChunkHeader& sut) { SCOPED_TRACE(std::string("Check user-payload not overlapping with ChunkHeader")); @@ -397,6 +411,7 @@ TEST_P(ChunkHeader_AlteringUserPayloadWithoutUserHeader, CheckIntegrityOfChunkHe constexpr uint32_t USER_HEADER_ALIGNMENT{iox::CHUNK_NO_USER_HEADER_ALIGNMENT}; createChunksOnMultipleAddresses(userPayloadParams, USER_HEADER_SIZE, USER_HEADER_ALIGNMENT, [&](ChunkHeader& sut) { + checkUserHeaderSizeAndPayloadSizeAndAlignmentIsSet(sut, userPayloadParams, USER_HEADER_SIZE); checkUserPayloadNotOverlappingWithChunkHeader(sut); checkUserPayloadSize(sut, userPayloadParams); checkUserPayloadAlignment(sut, userPayloadParams); @@ -468,6 +483,7 @@ TEST_P(ChunkHeader_AlteringUserPayloadWithUserHeader, CheckIntegrityOfChunkHeade createChunksOnMultipleAddresses( userPayloadParams, userHeaderSize, userHeaderAlignment, [&](ChunkHeader& sut) { + checkUserHeaderSizeAndPayloadSizeAndAlignmentIsSet(sut, userPayloadParams, userHeaderSize); checkUserHeaderIsAdjacentToChunkHeader(sut); checkUserPayloadNotOverlappingWithUserHeader(sut, userHeaderSize); checkUserPayloadSize(sut, userPayloadParams); From 027edd61442614173dc976713b35e30b50465946 Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Mon, 12 Apr 2021 20:05:35 +0200 Subject: [PATCH 018/127] iox-#14 disambiguate LoFFLi and adjust alignment for mempools Signed-off-by: Mathias Kraus --- iceoryx_posh/CMakeLists.txt | 1 + .../internal/mepoo/chunk_management.hpp | 7 +--- .../internal/mepoo/typed_mem_pool.inl | 12 +++--- .../include/iceoryx_posh/popo/listener.hpp | 7 ++-- .../source/mepoo/chunk_management.cpp | 39 +++++++++++++++++++ iceoryx_posh/source/mepoo/mem_pool.cpp | 8 ++-- iceoryx_posh/source/mepoo/memory_manager.cpp | 12 +++--- .../test/moduletests/test_mepoo_mempool.cpp | 5 ++- .../moduletests/test_mepoo_typed_mempool.cpp | 8 +++- .../internal/concurrent/loffli.hpp | 22 ++++++----- .../internal/concurrent/loffli.inl | 32 +++++++++++++++ iceoryx_utils/source/concurrent/loffli.cpp | 6 +-- .../moduletests/test_concurrent_loffli.cpp | 3 +- 13 files changed, 121 insertions(+), 41 deletions(-) create mode 100644 iceoryx_posh/source/mepoo/chunk_management.cpp create mode 100644 iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.inl diff --git a/iceoryx_posh/CMakeLists.txt b/iceoryx_posh/CMakeLists.txt index a1419e2ea56..79a212fb2d8 100644 --- a/iceoryx_posh/CMakeLists.txt +++ b/iceoryx_posh/CMakeLists.txt @@ -82,6 +82,7 @@ add_library(iceoryx_posh source/capro/capro_message.cpp source/capro/service_description.cpp source/mepoo/chunk_header.cpp + source/mepoo/chunk_management.cpp source/mepoo/chunk_settings.cpp source/mepoo/mepoo_config.cpp source/mepoo/segment_config.cpp diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/chunk_management.hpp b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/chunk_management.hpp index 2cfd78634f0..918dff8cf43 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/chunk_management.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/chunk_management.hpp @@ -39,12 +39,7 @@ struct ChunkManagement ChunkManagement(const cxx::not_null chunkHeader, const cxx::not_null mempool, - const cxx::not_null chunkManagementPool) noexcept - : m_chunkHeader(chunkHeader) - , m_mempool(mempool) - , m_chunkManagementPool(chunkManagementPool) - { - } + const cxx::not_null chunkManagementPool) noexcept; iox::rp::RelativePointer m_chunkHeader; referenceCounter_t m_referenceCounter{1U}; diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/typed_mem_pool.inl b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/typed_mem_pool.inl index 5f66bee0f60..a288e5d39b7 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/typed_mem_pool.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/typed_mem_pool.inl @@ -136,16 +136,18 @@ inline uint64_t TypedMemPool::requiredChunkSize() noexcept template inline uint64_t TypedMemPool::requiredManagementMemorySize(const uint64_t f_numberOfChunks) noexcept { - return f_numberOfChunks * sizeof(ChunkManagement) - + 2 - * cxx::align(static_cast(MemPool::freeList_t::requiredMemorySize(f_numberOfChunks)), - SHARED_MEMORY_ALIGNMENT); + uint64_t memorySizeForManagementPoolChunks = + cxx::align(f_numberOfChunks * sizeof(ChunkManagement), MemPool::CHUNK_MEMORY_ALIGNMENT); + uint64_t memorySizeForIndices = MemPool::freeList_t::requiredIndexMemorySize(f_numberOfChunks); + uint64_t memorySizeForIndicesOfManangementAndDataMemPools = + 2 * cxx::align(static_cast(memorySizeForIndices), MemPool::CHUNK_MEMORY_ALIGNMENT); + return memorySizeForManagementPoolChunks + memorySizeForIndicesOfManangementAndDataMemPools; } template inline uint64_t TypedMemPool::requiredChunkMemorySize(const uint64_t f_numberOfChunks) noexcept { - return f_numberOfChunks * requiredChunkSize(); + return cxx::align(f_numberOfChunks * requiredChunkSize(), MemPool::CHUNK_MEMORY_ALIGNMENT); } template diff --git a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp index cc2602b2062..b9dec9453e8 100644 --- a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp +++ b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp @@ -189,9 +189,10 @@ class Listener void push(const uint32_t index) noexcept; uint64_t indicesInUse() const noexcept; - uint32_t m_loffliStorage[concurrent::LoFFLi::requiredMemorySize(MAX_NUMBER_OF_EVENTS_PER_LISTENER) - / sizeof(uint32_t)]; - concurrent::LoFFLi m_loffli; + using LoFFLi = concurrent::LoFFLi; + LoFFLi::Index_t + m_loffliStorage[LoFFLi::requiredIndexMemorySize(MAX_NUMBER_OF_EVENTS_PER_LISTENER) / sizeof(uint32_t)]; + LoFFLi m_loffli; std::atomic m_indicesInUse{0U}; } m_indexManager; diff --git a/iceoryx_posh/source/mepoo/chunk_management.cpp b/iceoryx_posh/source/mepoo/chunk_management.cpp new file mode 100644 index 00000000000..8b7cfd03d2a --- /dev/null +++ b/iceoryx_posh/source/mepoo/chunk_management.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "iceoryx_posh/internal/mepoo/chunk_management.hpp" +#include "iceoryx_posh/internal/mepoo/mem_pool.hpp" + +namespace iox +{ +namespace mepoo +{ +ChunkManagement::ChunkManagement(const cxx::not_null chunkHeader, + const cxx::not_null mempool, + const cxx::not_null chunkManagementPool) noexcept + : m_chunkHeader(chunkHeader) + , m_mempool(mempool) + , m_chunkManagementPool(chunkManagementPool) +{ + static_assert(alignof(ChunkManagement) <= mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT, + "The ChunkManagement must not exceed the alignment of the mempool chunks, which are aligned to " + "'MemPool::CHUNK_MEMORY_ALIGNMENT'!"); +} + + +} // namespace mepoo +} // namespace iox diff --git a/iceoryx_posh/source/mepoo/mem_pool.cpp b/iceoryx_posh/source/mepoo/mem_pool.cpp index d968feabc9b..e7d815b3129 100644 --- a/iceoryx_posh/source/mepoo/mem_pool.cpp +++ b/iceoryx_posh/source/mepoo/mem_pool.cpp @@ -48,11 +48,11 @@ MemPool::MemPool(const cxx::greater_or_equal c { if (isMultipleOfAlignment(chunkSize)) { - m_rawMemory = - static_cast(chunkMemoryAllocator.allocate(static_cast(m_numberOfChunks) * m_chunkSize, CHUNK_MEMORY_ALIGNMENT)); + m_rawMemory = static_cast(chunkMemoryAllocator.allocate( + static_cast(m_numberOfChunks) * m_chunkSize, CHUNK_MEMORY_ALIGNMENT)); auto memoryLoFFLi = - static_cast(managementAllocator.allocate(freeList_t::requiredMemorySize(m_numberOfChunks))); - m_freeIndices.init(memoryLoFFLi, m_numberOfChunks); + managementAllocator.allocate(freeList_t::requiredIndexMemorySize(m_numberOfChunks), CHUNK_MEMORY_ALIGNMENT); + m_freeIndices.init(static_cast(memoryLoFFLi), m_numberOfChunks); } else { diff --git a/iceoryx_posh/source/mepoo/memory_manager.cpp b/iceoryx_posh/source/mepoo/memory_manager.cpp index 69bfdd44675..fb4c734b494 100644 --- a/iceoryx_posh/source/mepoo/memory_manager.cpp +++ b/iceoryx_posh/source/mepoo/memory_manager.cpp @@ -118,13 +118,15 @@ uint64_t MemoryManager::requiredManagementMemorySize(const MePooConfig& mePooCon for (const auto& mempool : mePooConfig.m_mempoolConfig) { sumOfAllChunks += mempool.m_chunkCount; - memorySize += cxx::align(static_cast(MemPool::freeList_t::requiredMemorySize(mempool.m_chunkCount)), - SHARED_MEMORY_ALIGNMENT); + memorySize += + cxx::align(static_cast(MemPool::freeList_t::requiredIndexMemorySize(mempool.m_chunkCount)), + MemPool::CHUNK_MEMORY_ALIGNMENT); } - memorySize += sumOfAllChunks * sizeof(ChunkManagement); - memorySize += cxx::align(static_cast(MemPool::freeList_t::requiredMemorySize(sumOfAllChunks)), - SHARED_MEMORY_ALIGNMENT); + memorySize += + cxx::align(static_cast(sumOfAllChunks * sizeof(ChunkManagement)), MemPool::CHUNK_MEMORY_ALIGNMENT); + memorySize += cxx::align(static_cast(MemPool::freeList_t::requiredIndexMemorySize(sumOfAllChunks)), + MemPool::CHUNK_MEMORY_ALIGNMENT); return memorySize; } diff --git a/iceoryx_posh/test/moduletests/test_mepoo_mempool.cpp b/iceoryx_posh/test/moduletests/test_mepoo_mempool.cpp index 38deb23fb94..90b5c6c6b8d 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_mempool.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_mempool.cpp @@ -27,8 +27,9 @@ class alignas(32) MemPool_test : public Test static constexpr uint32_t NumberOfChunks{100}; static constexpr uint32_t ChunkSize{64}; - static constexpr uint32_t LoFFLiMemoryRequirement{ - iox::mepoo::MemPool::freeList_t::requiredMemorySize(NumberOfChunks) + 10000}; + using FreeListIndex_t = iox::mepoo::MemPool::freeList_t::Index_t; + static constexpr FreeListIndex_t LoFFLiMemoryRequirement{ + iox::mepoo::MemPool::freeList_t::requiredIndexMemorySize(NumberOfChunks) + 10000}; MemPool_test() : allocator(m_rawMemory, NumberOfChunks * ChunkSize + LoFFLiMemoryRequirement) diff --git a/iceoryx_posh/test/moduletests/test_mepoo_typed_mempool.cpp b/iceoryx_posh/test/moduletests/test_mepoo_typed_mempool.cpp index 0e1612673e5..15ecfe2458e 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_typed_mempool.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_typed_mempool.cpp @@ -42,7 +42,9 @@ class alignas(32) TypedMemPool_test : public Test static constexpr uint32_t NumberOfChunks{3}; static constexpr uint32_t ChunkSize{128}; - static constexpr uint32_t LoFFLiMemoryRequirement{MemPool::freeList_t::requiredMemorySize(NumberOfChunks) + 100000}; + using FreeListIndex_t = MemPool::freeList_t::Index_t; + static constexpr FreeListIndex_t LoFFLiMemoryRequirement{ + MemPool::freeList_t::requiredIndexMemorySize(NumberOfChunks) + 100000}; TypedMemPool_test() : allocator(m_rawMemory, NumberOfChunks * ChunkSize + LoFFLiMemoryRequirement) @@ -114,7 +116,9 @@ class alignas(32) TypedMemPool_Semaphore_test : public Test static constexpr uint32_t NumberOfChunks{3}; static constexpr uint32_t ChunkSize{sizeof(iox::posix::Semaphore)}; - static constexpr uint32_t LoFFLiMemoryRequirement{MemPool::freeList_t::requiredMemorySize(NumberOfChunks) + 100000}; + using FreeListIndex_t = MemPool::freeList_t::Index_t; + static constexpr FreeListIndex_t LoFFLiMemoryRequirement{ + MemPool::freeList_t::requiredIndexMemorySize(NumberOfChunks) + 100000}; TypedMemPool_Semaphore_test() : allocator(m_rawMemory, NumberOfChunks * ChunkSize + LoFFLiMemoryRequirement) diff --git a/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.hpp b/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.hpp index de64b6f8100..77fae1b6fd5 100644 --- a/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.hpp +++ b/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.hpp @@ -29,11 +29,14 @@ namespace concurrent { class LoFFLi { + public: + using Index_t = uint32_t; + private: /// @todo std::atomic_is_lock_free check struct alignas(8) Node { - uint32_t indexToNextFreeIndex; + Index_t indexToNextFreeIndex; uint32_t abaCounter; }; @@ -61,9 +64,9 @@ class LoFFLi /// @endcode uint32_t m_size{0U}; - uint32_t m_invalidIndex{0U}; + Index_t m_invalidIndex{0U}; std::atomic m_head{{0U, 1U}}; - iox::rp::RelativePointer m_nextFreeIndex; + iox::rp::RelativePointer m_nextFreeIndex; public: LoFFLi() = default; @@ -73,28 +76,27 @@ class LoFFLi /// Initializes the lock-free free-list /// @param [in] freeIndicesMemory pointer to a memory with the size calculated by requiredMemorySize() /// @param [in] size is the number of elements of the free-list; must be the same used at requiredMemorySize() - void init(cxx::not_null freeIndicesMemory, const uint32_t size) noexcept; + void init(cxx::not_null freeIndicesMemory, const uint32_t size) noexcept; /// Pop a value from the free-list /// @param [out] index for an element to use /// @return true if index is valid, false otherwise - bool pop(uint32_t& index) noexcept; + bool pop(Index_t& index) noexcept; /// Push previously poped element /// @param [in] index to previously poped element /// @return true if index is valid or not yet pushed, false otherwise - bool push(const uint32_t index) noexcept; + bool push(const Index_t index) noexcept; /// Calculates the required memory size for a free-list /// @param [in] size is the number of elements of the free-list /// @return the required memory size for a free-list with size elements - static inline constexpr std::size_t requiredMemorySize(const uint32_t size) noexcept - { - return (static_cast(size) + 1U) * sizeof(uint32_t); - } + static inline constexpr std::size_t requiredIndexMemorySize(const uint32_t size) noexcept; }; } // namespace concurrent } // namespace iox +#include "loffli.inl" + #endif // IOX_UTILS_CONCURRENT_LOFFLI_HPP diff --git a/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.inl b/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.inl new file mode 100644 index 00000000000..b5b798445e4 --- /dev/null +++ b/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.inl @@ -0,0 +1,32 @@ +// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +#ifndef IOX_UTILS_CONCURRENT_LOFFLI_INL +#define IOX_UTILS_CONCURRENT_LOFFLI_INL + +namespace iox +{ +namespace concurrent +{ +inline constexpr std::size_t LoFFLi::requiredIndexMemorySize(const uint32_t size) noexcept +{ + return (static_cast(size) + 1U) * sizeof(LoFFLi::Index_t); +} + +} // namespace concurrent +} // namespace iox + +#endif // IOX_UTILS_CONCURRENT_LOFFLI_INL diff --git a/iceoryx_utils/source/concurrent/loffli.cpp b/iceoryx_utils/source/concurrent/loffli.cpp index 3b7d5bd644b..218b9abbf53 100644 --- a/iceoryx_utils/source/concurrent/loffli.cpp +++ b/iceoryx_utils/source/concurrent/loffli.cpp @@ -24,7 +24,7 @@ namespace iox { namespace concurrent { -void LoFFLi::init(cxx::not_null freeIndicesMemory, const uint32_t size) noexcept +void LoFFLi::init(cxx::not_null freeIndicesMemory, const uint32_t size) noexcept { cxx::Expects(size > 0); cxx::Expects(size <= UINT32_MAX - 2U); @@ -42,7 +42,7 @@ void LoFFLi::init(cxx::not_null freeIndicesMemory, const uint32_t siz } } -bool LoFFLi::pop(uint32_t& index) noexcept +bool LoFFLi::pop(Index_t& index) noexcept { Node oldHead = m_head.load(std::memory_order_acquire); Node newHead = oldHead; @@ -76,7 +76,7 @@ bool LoFFLi::pop(uint32_t& index) noexcept return true; } -bool LoFFLi::push(const uint32_t index) noexcept +bool LoFFLi::push(const Index_t index) noexcept { /// we synchronize with m_nextFreeIndex in pop to perform the validity check std::atomic_thread_fence(std::memory_order_release); diff --git a/iceoryx_utils/test/moduletests/test_concurrent_loffli.cpp b/iceoryx_utils/test/moduletests/test_concurrent_loffli.cpp index a96e234be32..91579679957 100644 --- a/iceoryx_utils/test/moduletests/test_concurrent_loffli.cpp +++ b/iceoryx_utils/test/moduletests/test_concurrent_loffli.cpp @@ -47,7 +47,8 @@ class LoFFLi_test : public Test { } - uint32_t m_memoryLoFFLi[LoFFLiType::requiredMemorySize(Size)]; + using LoFFLiIndex_t = typename LoFFLiType::Index_t; + LoFFLiIndex_t m_memoryLoFFLi[LoFFLiType::requiredIndexMemorySize(Size)]; LoFFLiType m_loffli; }; From 4be925d97229be3595d770f926d783080aedfcfd Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Mon, 12 Apr 2021 20:37:34 +0200 Subject: [PATCH 019/127] iox-#14 get rid of SHARED_MEMORY_ALIGNMENT Signed-off-by: Mathias Kraus --- .../iceoryx_posh/iceoryx_posh_types.hpp | 1 - .../internal/mepoo/segment_manager.inl | 9 +++----- iceoryx_posh/source/mepoo/mem_pool.cpp | 2 ++ iceoryx_posh/source/mepoo/memory_manager.cpp | 5 +++-- .../roudi/memory/default_roudi_memory.cpp | 22 ++++++------------- .../mempool_segment_manager_memory_block.cpp | 4 ++-- 6 files changed, 17 insertions(+), 26 deletions(-) diff --git a/iceoryx_posh/include/iceoryx_posh/iceoryx_posh_types.hpp b/iceoryx_posh/include/iceoryx_posh/iceoryx_posh_types.hpp index bd69352a966..527524952c7 100644 --- a/iceoryx_posh/include/iceoryx_posh/iceoryx_posh_types.hpp +++ b/iceoryx_posh/include/iceoryx_posh/iceoryx_posh_types.hpp @@ -120,7 +120,6 @@ static_assert(MAX_NUMBER_OF_EVENTS_PER_LISTENER <= MAX_NUMBER_OF_NOTIFIERS_PER_C constexpr uint32_t MAX_APPLICATION_CAPRO_FIFO_SIZE = 128U; // Memory -constexpr uint64_t SHARED_MEMORY_ALIGNMENT = 32U; constexpr uint32_t MAX_NUMBER_OF_MEMPOOLS = 32U; constexpr uint32_t MAX_SHM_SEGMENTS = 100U; diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl index 274cb53243c..7fbdd82cae4 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl @@ -150,8 +150,7 @@ uint64_t SegmentManager::requiredManagementMemorySize(const Segment uint64_t memorySize{0u}; for (auto segment : f_config.m_sharedMemorySegments) { - memorySize += - cxx::align(MemoryManager::requiredManagementMemorySize(segment.m_mempoolConfig), SHARED_MEMORY_ALIGNMENT); + memorySize += MemoryManager::requiredManagementMemorySize(segment.m_mempoolConfig); } return memorySize; } @@ -162,8 +161,7 @@ uint64_t SegmentManager::requiredChunkMemorySize(const SegmentConfi uint64_t memorySize{0u}; for (auto segment : f_config.m_sharedMemorySegments) { - memorySize += - cxx::align(MemoryManager::requiredChunkMemorySize(segment.m_mempoolConfig), SHARED_MEMORY_ALIGNMENT); + memorySize += MemoryManager::requiredChunkMemorySize(segment.m_mempoolConfig); } return memorySize; } @@ -171,8 +169,7 @@ uint64_t SegmentManager::requiredChunkMemorySize(const SegmentConfi template uint64_t SegmentManager::requiredFullMemorySize(const SegmentConfig& f_config) { - return cxx::align(requiredManagementMemorySize(f_config) + requiredChunkMemorySize(f_config), - SHARED_MEMORY_ALIGNMENT); + return requiredManagementMemorySize(f_config) + requiredChunkMemorySize(f_config); } } // namespace mepoo diff --git a/iceoryx_posh/source/mepoo/mem_pool.cpp b/iceoryx_posh/source/mepoo/mem_pool.cpp index e7d815b3129..823b6024aef 100644 --- a/iceoryx_posh/source/mepoo/mem_pool.cpp +++ b/iceoryx_posh/source/mepoo/mem_pool.cpp @@ -38,6 +38,8 @@ MemPoolInfo::MemPoolInfo(const uint32_t usedChunks, { } +constexpr uint64_t MemPool::CHUNK_MEMORY_ALIGNMENT; + MemPool::MemPool(const cxx::greater_or_equal chunkSize, const cxx::greater_or_equal numberOfChunks, posix::Allocator& managementAllocator, diff --git a/iceoryx_posh/source/mepoo/memory_manager.cpp b/iceoryx_posh/source/mepoo/memory_manager.cpp index fb4c734b494..25cd0dfa243 100644 --- a/iceoryx_posh/source/mepoo/memory_manager.cpp +++ b/iceoryx_posh/source/mepoo/memory_manager.cpp @@ -105,8 +105,9 @@ uint64_t MemoryManager::requiredChunkMemorySize(const MePooConfig& mePooConfig) // and the the chunk-payload size is taken into account; // the user has the option to further partition the chunk-payload with // a user-header and therefore reduce the user-payload size - memorySize += static_cast(mempoolConfig.m_chunkCount) - * MemoryManager::sizeWithChunkHeaderStruct(mempoolConfig.m_size); + memorySize += cxx::align(static_cast(mempoolConfig.m_chunkCount) + * MemoryManager::sizeWithChunkHeaderStruct(mempoolConfig.m_size), + MemPool::CHUNK_MEMORY_ALIGNMENT); } return memorySize; } diff --git a/iceoryx_posh/source/roudi/memory/default_roudi_memory.cpp b/iceoryx_posh/source/roudi/memory/default_roudi_memory.cpp index 614419e7080..be52348738b 100644 --- a/iceoryx_posh/source/roudi/memory/default_roudi_memory.cpp +++ b/iceoryx_posh/source/roudi/memory/default_roudi_memory.cpp @@ -16,6 +16,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "iceoryx_posh/roudi/memory/default_roudi_memory.hpp" +#include "iceoryx_posh/internal/mepoo/mem_pool.hpp" #include "iceoryx_posh/roudi/introspection_types.hpp" #include "iceoryx_utils/cxx/helplets.hpp" @@ -40,27 +41,18 @@ DefaultRouDiMemory::DefaultRouDiMemory(const RouDiConfig_t& roudiConfig) noexcep } mepoo::MePooConfig DefaultRouDiMemory::introspectionMemPoolConfig() const { + constexpr uint32_t ALIGNMENT{mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT}; mepoo::MePooConfig mempoolConfig; mempoolConfig.m_mempoolConfig.push_back( - {static_cast(cxx::align(static_cast(sizeof(roudi::MemPoolIntrospectionInfoContainer)), - SHARED_MEMORY_ALIGNMENT)), - 10}); + {cxx::align(static_cast(sizeof(roudi::MemPoolIntrospectionInfoContainer)), ALIGNMENT), 10}); mempoolConfig.m_mempoolConfig.push_back( - {static_cast( - cxx::align(static_cast(sizeof(roudi::ProcessIntrospectionFieldTopic)), SHARED_MEMORY_ALIGNMENT)), - 10}); + {cxx::align(static_cast(sizeof(roudi::ProcessIntrospectionFieldTopic)), ALIGNMENT), 10}); mempoolConfig.m_mempoolConfig.push_back( - {static_cast( - cxx::align(static_cast(sizeof(roudi::PortIntrospectionFieldTopic)), SHARED_MEMORY_ALIGNMENT)), - 10}); + {cxx::align(static_cast(sizeof(roudi::PortIntrospectionFieldTopic)), ALIGNMENT), 10}); mempoolConfig.m_mempoolConfig.push_back( - {static_cast(cxx::align(static_cast(sizeof(roudi::PortThroughputIntrospectionFieldTopic)), - SHARED_MEMORY_ALIGNMENT)), - 10}); + {cxx::align(static_cast(sizeof(roudi::PortThroughputIntrospectionFieldTopic)), ALIGNMENT), 10}); mempoolConfig.m_mempoolConfig.push_back( - {static_cast( - cxx::align(static_cast(sizeof(roudi::SubscriberPortChangingIntrospectionFieldTopic)), - SHARED_MEMORY_ALIGNMENT)), + {cxx::align(static_cast(sizeof(roudi::SubscriberPortChangingIntrospectionFieldTopic)), ALIGNMENT), 10}); mempoolConfig.optimize(); diff --git a/iceoryx_posh/source/roudi/memory/mempool_segment_manager_memory_block.cpp b/iceoryx_posh/source/roudi/memory/mempool_segment_manager_memory_block.cpp index ba80c1ad070..b4af6f11e49 100644 --- a/iceoryx_posh/source/roudi/memory/mempool_segment_manager_memory_block.cpp +++ b/iceoryx_posh/source/roudi/memory/mempool_segment_manager_memory_block.cpp @@ -34,13 +34,13 @@ MemPoolSegmentManagerMemoryBlock::~MemPoolSegmentManagerMemoryBlock() noexcept uint64_t MemPoolSegmentManagerMemoryBlock::size() const noexcept { - return cxx::align(static_cast(sizeof(mepoo::SegmentManager<>)), SHARED_MEMORY_ALIGNMENT) + return cxx::align(static_cast(sizeof(mepoo::SegmentManager<>)), mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT) + mepoo::SegmentManager<>::requiredManagementMemorySize(m_segmentConfig); } uint64_t MemPoolSegmentManagerMemoryBlock::alignment() const noexcept { - return algorithm::max(static_cast(alignof(mepoo::SegmentManager<>)), SHARED_MEMORY_ALIGNMENT); + return algorithm::max(static_cast(alignof(mepoo::SegmentManager<>)), mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT); } void MemPoolSegmentManagerMemoryBlock::memoryAvailable(void* memory) noexcept From 3810bac01ff6a2a74de5470990d2d9e3f8b5870c Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Mon, 12 Apr 2021 20:43:04 +0200 Subject: [PATCH 020/127] iox-#14 get rid of some alignas(32) Signed-off-by: Mathias Kraus --- .../roudi/memory/mempool_segment_manager_memory_block.cpp | 4 +++- iceoryx_posh/test/moduletests/test_mepoo_mempool.cpp | 7 ++++--- .../test/moduletests/test_mepoo_typed_mempool.cpp | 8 ++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/iceoryx_posh/source/roudi/memory/mempool_segment_manager_memory_block.cpp b/iceoryx_posh/source/roudi/memory/mempool_segment_manager_memory_block.cpp index b4af6f11e49..018ce420798 100644 --- a/iceoryx_posh/source/roudi/memory/mempool_segment_manager_memory_block.cpp +++ b/iceoryx_posh/source/roudi/memory/mempool_segment_manager_memory_block.cpp @@ -1,4 +1,5 @@ // Copyright (c) 2020 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -40,7 +41,8 @@ uint64_t MemPoolSegmentManagerMemoryBlock::size() const noexcept uint64_t MemPoolSegmentManagerMemoryBlock::alignment() const noexcept { - return algorithm::max(static_cast(alignof(mepoo::SegmentManager<>)), mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT); + return algorithm::max(static_cast(alignof(mepoo::SegmentManager<>)), + mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT); } void MemPoolSegmentManagerMemoryBlock::memoryAvailable(void* memory) noexcept diff --git a/iceoryx_posh/test/moduletests/test_mepoo_mempool.cpp b/iceoryx_posh/test/moduletests/test_mepoo_mempool.cpp index 90b5c6c6b8d..c81b9a426ab 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_mempool.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_mempool.cpp @@ -20,8 +20,9 @@ #include "test.hpp" using namespace ::testing; +using namespace iox::mepoo; -class alignas(32) MemPool_test : public Test +class MemPool_test : public Test { public: static constexpr uint32_t NumberOfChunks{100}; @@ -40,10 +41,10 @@ class alignas(32) MemPool_test : public Test void SetUp(){}; void TearDown(){}; - alignas(32) uint8_t m_rawMemory[NumberOfChunks * ChunkSize + LoFFLiMemoryRequirement]; + alignas(MemPool::CHUNK_MEMORY_ALIGNMENT) uint8_t m_rawMemory[NumberOfChunks * ChunkSize + LoFFLiMemoryRequirement]; iox::posix::Allocator allocator; - iox::mepoo::MemPool sut; + MemPool sut; }; TEST_F(MemPool_test, CTor) diff --git a/iceoryx_posh/test/moduletests/test_mepoo_typed_mempool.cpp b/iceoryx_posh/test/moduletests/test_mepoo_typed_mempool.cpp index 15ecfe2458e..4d15ed20932 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_typed_mempool.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_typed_mempool.cpp @@ -24,7 +24,7 @@ using namespace ::testing; using namespace iox::mepoo; -class alignas(32) TypedMemPool_test : public Test +class TypedMemPool_test : public Test { public: class TestClass @@ -55,7 +55,7 @@ class alignas(32) TypedMemPool_test : public Test void SetUp(){}; void TearDown(){}; - alignas(32) uint8_t m_rawMemory[NumberOfChunks * ChunkSize + LoFFLiMemoryRequirement]; + alignas(MemPool::CHUNK_MEMORY_ALIGNMENT) uint8_t m_rawMemory[NumberOfChunks * ChunkSize + LoFFLiMemoryRequirement]; iox::posix::Allocator allocator; TypedMemPool sut; @@ -110,7 +110,7 @@ TEST_F(TypedMemPool_test, OutOfChunksErrorWhenFull) EXPECT_THAT(object4.get_error(), Eq(TypedMemPoolError::OutOfChunks)); } -class alignas(32) TypedMemPool_Semaphore_test : public Test +class TypedMemPool_Semaphore_test : public Test { public: static constexpr uint32_t NumberOfChunks{3}; @@ -129,7 +129,7 @@ class alignas(32) TypedMemPool_Semaphore_test : public Test void SetUp(){}; void TearDown(){}; - alignas(32) uint8_t m_rawMemory[NumberOfChunks * ChunkSize + LoFFLiMemoryRequirement]; + alignas(MemPool::CHUNK_MEMORY_ALIGNMENT) uint8_t m_rawMemory[NumberOfChunks * ChunkSize + LoFFLiMemoryRequirement]; iox::posix::Allocator allocator; TypedMemPool sut; From d7c4f290ae4de579d4b77df23d9ffc2a8e474fdd Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Mon, 12 Apr 2021 20:47:42 +0200 Subject: [PATCH 021/127] iox-#14 get rid of alignas(alignof(T)) Signed-off-by: Mathias Kraus --- iceoryx_utils/include/iceoryx_utils/cxx/forward_list.hpp | 2 +- iceoryx_utils/include/iceoryx_utils/cxx/list.hpp | 2 +- iceoryx_utils/include/iceoryx_utils/cxx/optional.hpp | 2 +- iceoryx_utils/include/iceoryx_utils/cxx/stack.hpp | 2 +- iceoryx_utils/include/iceoryx_utils/cxx/vector.hpp | 2 +- .../iceoryx_utils/internal/concurrent/lockfree_queue/buffer.hpp | 2 +- .../include/iceoryx_utils/internal/objectpool/objectpool.hpp | 2 +- iceoryx_utils/test/moduletests/test_cxx_forward_list.cpp | 2 +- iceoryx_utils/test/moduletests/test_cxx_list.cpp | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/iceoryx_utils/include/iceoryx_utils/cxx/forward_list.hpp b/iceoryx_utils/include/iceoryx_utils/cxx/forward_list.hpp index 0ed97274e3b..a9d6f883924 100755 --- a/iceoryx_utils/include/iceoryx_utils/cxx/forward_list.hpp +++ b/iceoryx_utils/include/iceoryx_utils/cxx/forward_list.hpp @@ -351,7 +351,7 @@ class forward_list NodeLink m_links[NODE_LINK_COUNT]; using element_t = uint8_t[sizeof(T)]; - alignas(alignof(T)) element_t m_data[Capacity]; + alignas(T) element_t m_data[Capacity]; size_type m_size{0U}; }; // forward_list diff --git a/iceoryx_utils/include/iceoryx_utils/cxx/list.hpp b/iceoryx_utils/include/iceoryx_utils/cxx/list.hpp index e2ddb684df6..17b383055ca 100755 --- a/iceoryx_utils/include/iceoryx_utils/cxx/list.hpp +++ b/iceoryx_utils/include/iceoryx_utils/cxx/list.hpp @@ -379,7 +379,7 @@ class list // first used list element (begin()) NodeLink m_links[NODE_LINK_COUNT]; using element_t = uint8_t[sizeof(T)]; - alignas(alignof(T)) element_t m_data[Capacity]; + alignas(T) element_t m_data[Capacity]; size_type m_size{0U}; }; // list diff --git a/iceoryx_utils/include/iceoryx_utils/cxx/optional.hpp b/iceoryx_utils/include/iceoryx_utils/cxx/optional.hpp index 81f43fa903b..6a671fd3e5b 100644 --- a/iceoryx_utils/include/iceoryx_utils/cxx/optional.hpp +++ b/iceoryx_utils/include/iceoryx_utils/cxx/optional.hpp @@ -239,7 +239,7 @@ class optional const optional& or_else(const cxx::function_ref& callable) const noexcept; private: - alignas(alignof(T)) byte_t m_data[sizeof(T)]; + alignas(T) byte_t m_data[sizeof(T)]; bool m_hasValue{false}; private: diff --git a/iceoryx_utils/include/iceoryx_utils/cxx/stack.hpp b/iceoryx_utils/include/iceoryx_utils/cxx/stack.hpp index 49392687575..d51968eb723 100644 --- a/iceoryx_utils/include/iceoryx_utils/cxx/stack.hpp +++ b/iceoryx_utils/include/iceoryx_utils/cxx/stack.hpp @@ -49,7 +49,7 @@ class stack private: using element_t = uint8_t[sizeof(T)]; - alignas(alignof(T)) element_t m_data[Capacity]; + alignas(T) element_t m_data[Capacity]; uint64_t m_size = 0U; }; } // namespace cxx diff --git a/iceoryx_utils/include/iceoryx_utils/cxx/vector.hpp b/iceoryx_utils/include/iceoryx_utils/cxx/vector.hpp index 7e9b7563061..4a51a9b81a9 100644 --- a/iceoryx_utils/include/iceoryx_utils/cxx/vector.hpp +++ b/iceoryx_utils/include/iceoryx_utils/cxx/vector.hpp @@ -184,7 +184,7 @@ class vector private: using element_t = uint8_t[sizeof(T)]; - alignas(alignof(T)) element_t m_data[Capacity]; + alignas(T) element_t m_data[Capacity]; uint64_t m_size = 0u; }; } // namespace cxx diff --git a/iceoryx_utils/include/iceoryx_utils/internal/concurrent/lockfree_queue/buffer.hpp b/iceoryx_utils/include/iceoryx_utils/internal/concurrent/lockfree_queue/buffer.hpp index c60cbe7c8c8..669dea2f7f8 100644 --- a/iceoryx_utils/include/iceoryx_utils/internal/concurrent/lockfree_queue/buffer.hpp +++ b/iceoryx_utils/include/iceoryx_utils/internal/concurrent/lockfree_queue/buffer.hpp @@ -50,7 +50,7 @@ class Buffer private: using byte_t = uint8_t; - alignas(alignof(ElementType)) byte_t m_buffer[Capacity * sizeof(ElementType)]; + alignas(ElementType) byte_t m_buffer[Capacity * sizeof(ElementType)]; ElementType* toPtr(index_t index) const noexcept; }; diff --git a/iceoryx_utils/include/iceoryx_utils/internal/objectpool/objectpool.hpp b/iceoryx_utils/include/iceoryx_utils/internal/objectpool/objectpool.hpp index 99264086598..01fb24862e6 100644 --- a/iceoryx_utils/include/iceoryx_utils/internal/objectpool/objectpool.hpp +++ b/iceoryx_utils/include/iceoryx_utils/internal/objectpool/objectpool.hpp @@ -58,7 +58,7 @@ class ObjectPool T* data{nullptr}; }; - alignas(alignof(T)) Container m_values; + alignas(T) Container m_values; CellInfo m_cellInfo[CAPACITY]; char* m_first; char* m_last; diff --git a/iceoryx_utils/test/moduletests/test_cxx_forward_list.cpp b/iceoryx_utils/test/moduletests/test_cxx_forward_list.cpp index 806488febb5..83b499e3ede 100644 --- a/iceoryx_utils/test/moduletests/test_cxx_forward_list.cpp +++ b/iceoryx_utils/test/moduletests/test_cxx_forward_list.cpp @@ -1929,7 +1929,7 @@ TEST_F(forward_list_test, ListIsCopyableViaMemcpy) { uint64_t i = 0U; using TestFwdList = forward_list; - alignas(alignof(TestFwdList)) uint8_t otherSutBuffer[sizeof(TestFwdList)]; + alignas(TestFwdList) uint8_t otherSutBuffer[sizeof(TestFwdList)]; uint8_t* otherSutPtr = otherSutBuffer; { diff --git a/iceoryx_utils/test/moduletests/test_cxx_list.cpp b/iceoryx_utils/test/moduletests/test_cxx_list.cpp index d7cd04b3398..d98e64d869c 100644 --- a/iceoryx_utils/test/moduletests/test_cxx_list.cpp +++ b/iceoryx_utils/test/moduletests/test_cxx_list.cpp @@ -2253,7 +2253,7 @@ TEST_F(list_test, ListIsCopyableViaMemcpy) { uint64_t i = 0U; using TestFwdList = list; - alignas(alignof(TestFwdList)) uint8_t otherSutBuffer[sizeof(TestFwdList)]; + alignas(TestFwdList) uint8_t otherSutBuffer[sizeof(TestFwdList)]; uint8_t* otherSutPtr = otherSutBuffer; { From 581c46aec74f5ce99521916343c493c7995fb8f5 Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Mon, 12 Apr 2021 20:57:39 +0200 Subject: [PATCH 022/127] iox-##529 remove 'define private public' from test_roudi_service_registry.cpp Signed-off-by: Mathias Kraus --- iceoryx_posh/test/moduletests/test_roudi_service_registry.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/iceoryx_posh/test/moduletests/test_roudi_service_registry.cpp b/iceoryx_posh/test/moduletests/test_roudi_service_registry.cpp index 40261b33060..f8c885cb956 100644 --- a/iceoryx_posh/test/moduletests/test_roudi_service_registry.cpp +++ b/iceoryx_posh/test/moduletests/test_roudi_service_registry.cpp @@ -16,12 +16,8 @@ #include "test.hpp" -#define private public -#define protected public #include "iceoryx_posh/internal/roudi/service_registry.hpp" #include "iceoryx_utils/cxx/string.hpp" -#undef protected -#undef private using namespace ::testing; using ::testing::Return; From 0a744c223b78817de553e49b3a0ab6f3e751184d Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Mon, 12 Apr 2021 21:46:55 +0200 Subject: [PATCH 023/127] iox-#14 remove default alignment parameter from Allocator::allocate Signed-off-by: Mathias Kraus --- .../posix_wrapper/shared_memory_object.hpp | 2 +- .../shared_memory_object/allocator.hpp | 14 +++++++-- .../shared_memory_object/allocator.cpp | 30 +++++++++---------- .../test/moduletests/test_allocator.cpp | 18 ++++++----- 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object.hpp b/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object.hpp index 7aa159a10d6..28bb3fdf4af 100644 --- a/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object.hpp +++ b/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object.hpp @@ -49,7 +49,7 @@ class SharedMemoryObject : public DesignPattern::Creation(f_startAddress)) - , m_length(f_length) +Allocator::Allocator(void* const startAddress, const uint64_t length) noexcept + : m_startAddress(static_cast(startAddress)) + , m_length(length) { /// @todo memset to set memory and to avoid the usage of unavailable memory } -void* Allocator::allocate(const uint64_t f_size, const uint64_t f_alignment) noexcept +void* Allocator::allocate(const uint64_t size, const uint64_t alignment) noexcept { - cxx::Expects(f_size > 0); + cxx::Expects(size > 0); if (m_allocationFinalized) { @@ -44,23 +45,22 @@ void* Allocator::allocate(const uint64_t f_size, const uint64_t f_alignment) noe std::terminate(); } - uint64_t l_currentAddress = reinterpret_cast(m_startAddress) + m_currentPosition; - uint64_t l_alignedPosition = cxx::align(l_currentAddress, static_cast(f_alignment)); - l_alignedPosition -= reinterpret_cast(m_startAddress); + uint64_t currentAddress = reinterpret_cast(m_startAddress) + m_currentPosition; + uint64_t alignedPosition = cxx::align(currentAddress, static_cast(alignment)); + alignedPosition -= reinterpret_cast(m_startAddress); byte_t* l_returnValue = nullptr; - if (m_length >= l_alignedPosition + f_size) + if (m_length >= alignedPosition + size) { - l_returnValue = m_startAddress + l_alignedPosition; - m_currentPosition = l_alignedPosition + f_size; + l_returnValue = m_startAddress + alignedPosition; + m_currentPosition = alignedPosition + size; } else { - std::cerr << "Trying to allocate additional " << f_size << " bytes in the shared memory of capacity " - << m_length << " when there are already " << l_alignedPosition << " aligned bytes in use." - << std::endl; - std::cerr << "Only " << m_length - l_alignedPosition << " bytes left." << std::endl; + std::cerr << "Trying to allocate additional " << size << " bytes in the shared memory of capacity " << m_length + << " when there are already " << alignedPosition << " aligned bytes in use." << std::endl; + std::cerr << "Only " << m_length - alignedPosition << " bytes left." << std::endl; std::terminate(); } diff --git a/iceoryx_utils/test/moduletests/test_allocator.cpp b/iceoryx_utils/test/moduletests/test_allocator.cpp index ca536be2f82..ceaf4cb9eb7 100644 --- a/iceoryx_utils/test/moduletests/test_allocator.cpp +++ b/iceoryx_utils/test/moduletests/test_allocator.cpp @@ -35,6 +35,8 @@ class Allocator_Test : public Test free(memory); } + static constexpr uint64_t MEMORY_ALIGNMENT{iox::posix::Allocator::MEMORY_ALIGNMENT}; + void* memory; size_t memorySize = 10016; }; @@ -42,7 +44,7 @@ class Allocator_Test : public Test TEST_F(Allocator_Test, allocateOneSmallElement) { iox::posix::Allocator sut(memory, memorySize); - int* bla = static_cast(sut.allocate(sizeof(int))); + int* bla = static_cast(sut.allocate(sizeof(int), MEMORY_ALIGNMENT)); *bla = 123; EXPECT_THAT(*bla, Eq(123)); } @@ -70,7 +72,7 @@ TEST_F(Allocator_Test, allocateTooMuchSingleElement) { iox::posix::Allocator sut(memory, memorySize); std::set_terminate([]() { std::cout << "", std::abort(); }); - EXPECT_DEATH({ sut.allocate(memorySize + 1); }, ".*"); + EXPECT_DEATH({ sut.allocate(memorySize + 1, MEMORY_ALIGNMENT); }, ".*"); } TEST_F(Allocator_Test, allocateTooMuchMultipleElement) @@ -82,21 +84,21 @@ TEST_F(Allocator_Test, allocateTooMuchMultipleElement) } std::set_terminate([]() { std::cout << "", std::abort(); }); - EXPECT_DEATH({ sut.allocate(1); }, ".*"); + EXPECT_DEATH({ sut.allocate(1, MEMORY_ALIGNMENT); }, ".*"); } TEST_F(Allocator_Test, allocateAndAlignment) { iox::posix::Allocator sut(memory, memorySize); - auto bla = static_cast(sut.allocate(5)); - auto bla2 = static_cast(sut.allocate(5)); + auto bla = static_cast(sut.allocate(5, MEMORY_ALIGNMENT)); + auto bla2 = static_cast(sut.allocate(5, MEMORY_ALIGNMENT)); EXPECT_THAT(bla2 - bla, Eq(32)); } TEST_F(Allocator_Test, allocateElementOfSizeZero) { iox::posix::Allocator sut(memory, memorySize); - EXPECT_DEATH(sut.allocate(0), ".*"); + EXPECT_DEATH(sut.allocate(0, MEMORY_ALIGNMENT), ".*"); } TEST_F(Allocator_Test, allocateAfterFinalizeAllocation) @@ -112,10 +114,10 @@ TEST_F(Allocator_Test, allocateAfterFinalizeAllocation) using iox::posix::Allocator::finalizeAllocation; }; AllocatorAccess sut(memory, memorySize); - sut.allocate(5); + sut.allocate(5, MEMORY_ALIGNMENT); sut.finalizeAllocation(); std::set_terminate([]() { std::cout << "", std::abort(); }); - EXPECT_DEATH({ sut.allocate(5); }, ".*"); + EXPECT_DEATH({ sut.allocate(5, MEMORY_ALIGNMENT); }, ".*"); } } // namespace From 704d34c8a477e89b23ab2f8c580a4b30381f07ca Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Mon, 12 Apr 2021 22:44:12 +0200 Subject: [PATCH 024/127] iox-#14 fix test Signed-off-by: Mathias Kraus --- iceoryx_utils/test/moduletests/test_allocator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iceoryx_utils/test/moduletests/test_allocator.cpp b/iceoryx_utils/test/moduletests/test_allocator.cpp index ceaf4cb9eb7..c85fdf1df3c 100644 --- a/iceoryx_utils/test/moduletests/test_allocator.cpp +++ b/iceoryx_utils/test/moduletests/test_allocator.cpp @@ -92,7 +92,7 @@ TEST_F(Allocator_Test, allocateAndAlignment) iox::posix::Allocator sut(memory, memorySize); auto bla = static_cast(sut.allocate(5, MEMORY_ALIGNMENT)); auto bla2 = static_cast(sut.allocate(5, MEMORY_ALIGNMENT)); - EXPECT_THAT(bla2 - bla, Eq(32)); + EXPECT_THAT(bla2 - bla, Eq(8U)); } TEST_F(Allocator_Test, allocateElementOfSizeZero) From 32cf4203b9d47b621776a4966594ee0b205c2f75 Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Tue, 13 Apr 2021 11:32:31 +0200 Subject: [PATCH 025/127] iox-#14 make all ChunkHeader methods noexcept Signed-off-by: Mathias Kraus --- iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp | 8 ++++---- iceoryx_posh/source/mepoo/chunk_header.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp b/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp index db0d7924366..fa83cd90ec3 100644 --- a/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp +++ b/iceoryx_posh/include/iceoryx_posh/mepoo/chunk_header.hpp @@ -113,19 +113,19 @@ struct ChunkHeader /// @brief The unique identifier of the publisher the chunk was sent from /// @return the id of the publisher the chunk was sent from - UniquePortId originId() const; + UniquePortId originId() const noexcept; /// @brief A serial number for the sent chunks /// @brief the serquence number of the chunk - uint64_t sequenceNumber() const; + uint64_t sequenceNumber() const noexcept; private: template friend class popo::ChunkSender; - void setOriginId(UniquePortId originId); + void setOriginId(UniquePortId originId) noexcept; - void setSequenceNumber(uint64_t sequenceNumber); + void setSequenceNumber(uint64_t sequenceNumber) noexcept; uint64_t overflowSafeUsedSizeOfChunk() const noexcept; diff --git a/iceoryx_posh/source/mepoo/chunk_header.cpp b/iceoryx_posh/source/mepoo/chunk_header.cpp index ba341ea2f2f..eb770fb74ce 100644 --- a/iceoryx_posh/source/mepoo/chunk_header.cpp +++ b/iceoryx_posh/source/mepoo/chunk_header.cpp @@ -173,22 +173,22 @@ uint32_t ChunkHeader::userPayloadAlignment() const noexcept return m_userPayloadAlignment; } -UniquePortId ChunkHeader::originId() const +UniquePortId ChunkHeader::originId() const noexcept { return m_originId; } -void ChunkHeader::setOriginId(UniquePortId originId) +void ChunkHeader::setOriginId(UniquePortId originId) noexcept { m_originId = originId; } -uint64_t ChunkHeader::sequenceNumber() const +uint64_t ChunkHeader::sequenceNumber() const noexcept { return m_sequenceNumber; } -void ChunkHeader::setSequenceNumber(uint64_t sequenceNumber) +void ChunkHeader::setSequenceNumber(uint64_t sequenceNumber) noexcept { m_sequenceNumber = sequenceNumber; } From e0616739b5daf38470e1b57dd23487e54967145e Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Tue, 13 Apr 2021 11:38:49 +0200 Subject: [PATCH 026/127] iox-#14 remove magic number for introspection mempool Signed-off-by: Mathias Kraus --- .../source/roudi/memory/default_roudi_memory.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/iceoryx_posh/source/roudi/memory/default_roudi_memory.cpp b/iceoryx_posh/source/roudi/memory/default_roudi_memory.cpp index be52348738b..a35a19043a1 100644 --- a/iceoryx_posh/source/roudi/memory/default_roudi_memory.cpp +++ b/iceoryx_posh/source/roudi/memory/default_roudi_memory.cpp @@ -42,18 +42,23 @@ DefaultRouDiMemory::DefaultRouDiMemory(const RouDiConfig_t& roudiConfig) noexcep mepoo::MePooConfig DefaultRouDiMemory::introspectionMemPoolConfig() const { constexpr uint32_t ALIGNMENT{mepoo::MemPool::CHUNK_MEMORY_ALIGNMENT}; + // have some spare chunks to still deliver introspection data in case there are multiple subscriber to the data + // which are caching different samples; could probably be reduced to 2 with the instruction to not cache the + // introspection samples + constexpr uint32_t CHUNK_COUNT{10U}; mepoo::MePooConfig mempoolConfig; mempoolConfig.m_mempoolConfig.push_back( - {cxx::align(static_cast(sizeof(roudi::MemPoolIntrospectionInfoContainer)), ALIGNMENT), 10}); + {cxx::align(static_cast(sizeof(roudi::MemPoolIntrospectionInfoContainer)), ALIGNMENT), CHUNK_COUNT}); mempoolConfig.m_mempoolConfig.push_back( - {cxx::align(static_cast(sizeof(roudi::ProcessIntrospectionFieldTopic)), ALIGNMENT), 10}); + {cxx::align(static_cast(sizeof(roudi::ProcessIntrospectionFieldTopic)), ALIGNMENT), CHUNK_COUNT}); mempoolConfig.m_mempoolConfig.push_back( - {cxx::align(static_cast(sizeof(roudi::PortIntrospectionFieldTopic)), ALIGNMENT), 10}); + {cxx::align(static_cast(sizeof(roudi::PortIntrospectionFieldTopic)), ALIGNMENT), CHUNK_COUNT}); mempoolConfig.m_mempoolConfig.push_back( - {cxx::align(static_cast(sizeof(roudi::PortThroughputIntrospectionFieldTopic)), ALIGNMENT), 10}); + {cxx::align(static_cast(sizeof(roudi::PortThroughputIntrospectionFieldTopic)), ALIGNMENT), + CHUNK_COUNT}); mempoolConfig.m_mempoolConfig.push_back( {cxx::align(static_cast(sizeof(roudi::SubscriberPortChangingIntrospectionFieldTopic)), ALIGNMENT), - 10}); + CHUNK_COUNT}); mempoolConfig.optimize(); return mempoolConfig; From 8aabe1f3b4c830d698831fd1a1e5227a0088524d Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Tue, 13 Apr 2021 12:01:45 +0200 Subject: [PATCH 027/127] iox-#14 ensure 'LoFFLi::Node' is lock-free and fix typo Signed-off-by: Mathias Kraus --- .../internal/concurrent/loffli.hpp | 17 +++++++++-------- .../internal/concurrent/loffli.inl | 4 ++-- .../shared_memory_object/allocator.hpp | 2 +- iceoryx_utils/source/concurrent/loffli.cpp | 11 +++++++---- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.hpp b/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.hpp index 77fae1b6fd5..6b9494dc1d7 100644 --- a/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.hpp +++ b/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.hpp @@ -33,13 +33,15 @@ class LoFFLi using Index_t = uint32_t; private: - /// @todo std::atomic_is_lock_free check struct alignas(8) Node { Index_t indexToNextFreeIndex; uint32_t abaCounter; }; + static_assert(sizeof(Node) <= 8U, + "The size of 'Node' must not exceed 8 bytes in order to be lock-free on 64 bit systems!"); + /// @todo introduce typesafe indices with the properties listed below /// id is required that not two loefflis with the same properties /// mix up the id @@ -71,12 +73,11 @@ class LoFFLi public: LoFFLi() = default; /// @todo: why init not in ctor - /// @todo: size = capacity /// Initializes the lock-free free-list - /// @param [in] freeIndicesMemory pointer to a memory with the size calculated by requiredMemorySize() - /// @param [in] size is the number of elements of the free-list; must be the same used at requiredMemorySize() - void init(cxx::not_null freeIndicesMemory, const uint32_t size) noexcept; + /// @param [in] freeIndicesMemory pointer to a memory with the capacity calculated by requiredMemorySize() + /// @param [in] capacity is the number of elements of the free-list; must be the same used at requiredMemorySize() + void init(cxx::not_null freeIndicesMemory, const uint32_t capacity) noexcept; /// Pop a value from the free-list /// @param [out] index for an element to use @@ -89,9 +90,9 @@ class LoFFLi bool push(const Index_t index) noexcept; /// Calculates the required memory size for a free-list - /// @param [in] size is the number of elements of the free-list - /// @return the required memory size for a free-list with size elements - static inline constexpr std::size_t requiredIndexMemorySize(const uint32_t size) noexcept; + /// @param [in] capacity is the number of elements of the free-list + /// @return the required memory size for a free-list with the requested capacity + static inline constexpr std::size_t requiredIndexMemorySize(const uint32_t capacity) noexcept; }; } // namespace concurrent diff --git a/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.inl b/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.inl index b5b798445e4..d73ceb30057 100644 --- a/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.inl +++ b/iceoryx_utils/include/iceoryx_utils/internal/concurrent/loffli.inl @@ -21,9 +21,9 @@ namespace iox { namespace concurrent { -inline constexpr std::size_t LoFFLi::requiredIndexMemorySize(const uint32_t size) noexcept +inline constexpr std::size_t LoFFLi::requiredIndexMemorySize(const uint32_t capacity) noexcept { - return (static_cast(size) + 1U) * sizeof(LoFFLi::Index_t); + return (static_cast(capacity) + 1U) * sizeof(LoFFLi::Index_t); } } // namespace concurrent diff --git a/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object/allocator.hpp b/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object/allocator.hpp index 4359886cdd9..aa1df4ad44e 100644 --- a/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object/allocator.hpp +++ b/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object/allocator.hpp @@ -31,7 +31,7 @@ class Allocator public: static constexpr uint64_t MEMORY_ALIGNMENT = 8U; - /// @brief A bumb allocator for the memory provided in the ctor arguments + /// @brief A bump allocator for the memory provided in the ctor arguments /// @param[in] startAddress of the memory this allocator manages /// @param[in] length of the memory this allocator manages Allocator(void* const startAddress, const uint64_t length) noexcept; diff --git a/iceoryx_utils/source/concurrent/loffli.cpp b/iceoryx_utils/source/concurrent/loffli.cpp index 218b9abbf53..48105953990 100644 --- a/iceoryx_utils/source/concurrent/loffli.cpp +++ b/iceoryx_utils/source/concurrent/loffli.cpp @@ -24,13 +24,16 @@ namespace iox { namespace concurrent { -void LoFFLi::init(cxx::not_null freeIndicesMemory, const uint32_t size) noexcept +void LoFFLi::init(cxx::not_null freeIndicesMemory, const uint32_t capacity) noexcept { - cxx::Expects(size > 0); - cxx::Expects(size <= UINT32_MAX - 2U); + cxx::Expects(capacity > 0 && "A capacity of 0 is not supported!"); + constexpr uint32_t INTERNALLY_RESERVED_INDICES{1U}; + cxx::Expects(capacity < (std::numeric_limits::max() - INTERNALLY_RESERVED_INDICES) + && "Requested capacityexceeds limits!"); + cxx::Expects(m_head.is_lock_free() && "std::atomic must be lock-free!"); m_nextFreeIndex = freeIndicesMemory; - m_size = size; + m_size = capacity; m_invalidIndex = m_size + 1; if (m_nextFreeIndex != nullptr) From dfc80a719cb5d60622c667d394320d33e72fbd1e Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Mon, 12 Apr 2021 21:51:31 +0200 Subject: [PATCH 028/127] iox-#707 extended listener with user defined type in callback Signed-off-by: Christian Eltzschig --- .../iceoryx_posh/internal/popo/listener.inl | 74 +++++++++++++++++-- .../include/iceoryx_posh/popo/listener.hpp | 33 +++++++-- iceoryx_posh/source/popo/listener.cpp | 11 ++- .../test/moduletests/test_popo_listener.cpp | 39 ++++++++++ 4 files changed, 141 insertions(+), 16 deletions(-) diff --git a/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl b/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl index 559d4e95e81..3ba4976e436 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl @@ -22,21 +22,36 @@ namespace popo { namespace internal { +template +struct TranslateAndCallTypelessCallback +{ + static void call(void* const origin, void* const userType, Listener::GenericCallbackPtr_t underlyingCallback) + { + reinterpret_cast>(underlyingCallback)( + static_cast(origin), static_cast(userType)); + } +}; + template -inline void translateAndCallTypelessCallback(void* const origin, void (*underlyingCallback)(void* const)) +struct TranslateAndCallTypelessCallback { - reinterpret_cast(underlyingCallback)(static_cast(origin)); -} + static void call(void* const origin, void* const userType, Listener::GenericCallbackPtr_t underlyingCallback) + { + IOX_DISCARD_RESULT(userType); + reinterpret_cast>(underlyingCallback)(static_cast(origin)); + } +}; } // namespace internal template inline cxx::expected Listener::attachEvent(T& eventOrigin, CallbackRef_t eventCallback) noexcept { return addEvent(&eventOrigin, + nullptr, static_cast(NoEnumUsed::PLACEHOLDER), typeid(NoEnumUsed).hash_code(), - reinterpret_cast>(eventCallback), - internal::translateAndCallTypelessCallback, + reinterpret_cast(eventCallback), + internal::TranslateAndCallTypelessCallback::call, EventAttorney::getInvalidateTriggerMethod(eventOrigin)) .and_then([&](auto& eventId) { EventAttorney::enableEvent( @@ -51,10 +66,11 @@ Listener::attachEvent(T& eventOrigin, const EventType eventType, CallbackRef_t, "Only enums with an underlying EventEnumIdentifier can be attached/detached to the Listener"); return addEvent(&eventOrigin, + nullptr, static_cast(eventType), typeid(EventType).hash_code(), - reinterpret_cast>(eventCallback), - internal::translateAndCallTypelessCallback, + reinterpret_cast(eventCallback), + internal::TranslateAndCallTypelessCallback::call, EventAttorney::getInvalidateTriggerMethod(eventOrigin)) .and_then([&](auto& eventId) { EventAttorney::enableEvent( @@ -64,6 +80,50 @@ Listener::attachEvent(T& eventOrigin, const EventType eventType, CallbackRef_t::value>> +inline cxx::expected Listener::attachEvent(T& eventOrigin, + const EventType eventType, + CallbackWithUserTypeRef_t eventCallback, + UserType& userType) noexcept +{ + static_assert(IS_EVENT_ENUM, + "Only enums with an underlying EventEnumIdentifier can be attached/detached to the Listener"); + + return addEvent(&eventOrigin, + &userType, + static_cast(eventType), + typeid(EventType).hash_code(), + reinterpret_cast(eventCallback), + internal::TranslateAndCallTypelessCallback::call, + EventAttorney::getInvalidateTriggerMethod(eventOrigin)) + .and_then([&](auto& eventId) { + EventAttorney::enableEvent( + eventOrigin, + TriggerHandle(*m_conditionVariableData, {*this, &Listener::removeTrigger}, eventId), + eventType); + }); +} + +template +inline cxx::expected +Listener::attachEvent(T& eventOrigin, CallbackWithUserTypeRef_t eventCallback, UserType& userType) noexcept +{ + return addEvent(&eventOrigin, + &userType, + static_cast(NoEnumUsed::PLACEHOLDER), + typeid(NoEnumUsed).hash_code(), + reinterpret_cast(eventCallback), + internal::TranslateAndCallTypelessCallback::call, + EventAttorney::getInvalidateTriggerMethod(eventOrigin)) + .and_then([&](auto& eventId) { + EventAttorney::enableEvent( + eventOrigin, TriggerHandle(*m_conditionVariableData, {*this, &Listener::removeTrigger}, eventId)); + }); +} + template inline void Listener::detachEvent(T& eventOrigin, const EventType eventType) noexcept { diff --git a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp index cc2602b2062..1676c9377f8 100644 --- a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp +++ b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp @@ -65,13 +65,20 @@ enum class ListenerError class Listener { public: + using GenericCallbackPtr_t = void (*)(); + using GenericCallbackRef_t = void (&)(); + template using CallbackRef_t = void (&)(T* const); - using TranslationCallbackRef_t = void (&)(void* const, void (*const)(void* const)); + template + using CallbackWithUserTypeRef_t = void (&)(T* const, UserType* const); + using TranslationCallbackRef_t = void (&)(void* const, void* const, GenericCallbackPtr_t const); template using CallbackPtr_t = void (*)(T* const); - using TranslationCallbackPtr_t = void (*)(void* const, void (*const)(void* const)); + template + using CallbackWithUserTypePtr_t = void (*)(T* const, UserType* const); + using TranslationCallbackPtr_t = void (*)(void* const, void* const, GenericCallbackPtr_t const); Listener() noexcept; Listener(const Listener&) = delete; @@ -104,6 +111,19 @@ class Listener cxx::expected attachEvent(T& eventOrigin, const EventType eventType, CallbackRef_t eventCallback) noexcept; + template ::value>> + cxx::expected attachEvent(T& eventOrigin, + const EventType eventType, + CallbackWithUserTypeRef_t eventCallback, + UserType& userType) noexcept; + + template + cxx::expected + attachEvent(T& eventOrigin, CallbackWithUserTypeRef_t eventCallback, UserType& userType) noexcept; + /// @brief Detaches an event. Hereby, the event is defined as a class T, the eventOrigin and /// the eventType with further specifies the event inside of eventOrigin /// @note This method can be called from any thread concurrently without any restrictions! @@ -136,9 +156,10 @@ class Listener void threadLoop() noexcept; cxx::expected addEvent(void* const origin, + void* const userType, const uint64_t eventType, const uint64_t eventTypeHash, - CallbackRef_t callback, + GenericCallbackRef_t callback, TranslationCallbackRef_t translationCallback, const cxx::MethodCallback invalidationCallback) noexcept; @@ -159,9 +180,10 @@ class Listener bool reset() noexcept; bool init(const uint64_t eventId, void* const origin, + void* const userType, const uint64_t eventType, const uint64_t eventTypeHash, - CallbackRef_t callback, + GenericCallbackRef_t callback, TranslationCallbackRef_t translationCallback, const cxx::MethodCallback invalidationCallback) noexcept; void executeCallback() noexcept; @@ -174,8 +196,9 @@ class Listener uint64_t m_eventType = INVALID_ID; uint64_t m_eventTypeHash = INVALID_ID; - CallbackPtr_t m_callback = nullptr; + GenericCallbackPtr_t m_callback = nullptr; TranslationCallbackPtr_t m_translationCallback = nullptr; + void* m_userType = nullptr; uint64_t m_eventId = INVALID_ID; cxx::MethodCallback m_invalidationCallback; diff --git a/iceoryx_posh/source/popo/listener.cpp b/iceoryx_posh/source/popo/listener.cpp index 4c5557bcc65..a3bcee47a5f 100644 --- a/iceoryx_posh/source/popo/listener.cpp +++ b/iceoryx_posh/source/popo/listener.cpp @@ -45,9 +45,10 @@ Listener::~Listener() cxx::expected Listener::addEvent(void* const origin, + void* const userType, const uint64_t eventType, const uint64_t eventTypeHash, - CallbackRef_t callback, + GenericCallbackRef_t callback, TranslationCallbackRef_t translationCallback, const cxx::MethodCallback invalidationCallback) noexcept { @@ -68,7 +69,7 @@ Listener::addEvent(void* const origin, } if (!m_events[index]->init( - index, origin, eventType, eventTypeHash, callback, translationCallback, invalidationCallback)) + index, origin, userType, eventType, eventTypeHash, callback, translationCallback, invalidationCallback)) { return cxx::error(ListenerError::EMPTY_INVALIDATION_CALLBACK); } @@ -118,14 +119,15 @@ void Listener::Event_t::executeCallback() noexcept return; } - m_translationCallback(m_origin, m_callback); + m_translationCallback(m_origin, m_userType, m_callback); } bool Listener::Event_t::init(const uint64_t eventId, void* const origin, + void* const userType, const uint64_t eventType, const uint64_t eventTypeHash, - CallbackRef_t callback, + GenericCallbackRef_t callback, TranslationCallbackRef_t translationCallback, const cxx::MethodCallback invalidationCallback) noexcept { @@ -133,6 +135,7 @@ bool Listener::Event_t::init(const uint64_t eventId, { m_eventId = eventId; m_origin = origin; + m_userType = userType; m_eventType = eventType; m_eventTypeHash = eventTypeHash; m_callback = &callback; diff --git a/iceoryx_posh/test/moduletests/test_popo_listener.cpp b/iceoryx_posh/test/moduletests/test_popo_listener.cpp index 36d9c3503c2..532fa5a49c9 100644 --- a/iceoryx_posh/test/moduletests/test_popo_listener.cpp +++ b/iceoryx_posh/test/moduletests/test_popo_listener.cpp @@ -119,6 +119,10 @@ class SimpleEventClass { m_handleStoepsel.trigger(); } + void triggerNoEventType() noexcept + { + m_handleNoEventEnum.trigger(); + } iox::popo::TriggerHandle m_handleHypnotoad; iox::popo::TriggerHandle m_handleStoepsel; @@ -168,6 +172,12 @@ class Listener_test : public Test std::this_thread::sleep_for(std::chrono::milliseconds(g_triggerCallbackRuntimeInMs)); } + static void triggerCallbackWithUserType(SimpleEventClass* const event, uint64_t* userType) noexcept + { + g_triggerCallbackArg[0].m_source = event; + ++(*userType); + } + static void attachCallback(SimpleEventClass* const) noexcept { for (auto& e : g_toBeAttached.GetCopy()) @@ -548,6 +558,35 @@ TIMING_TEST_F(Listener_test, CallbackIsCalledAfterNotify, Repeat(5), [&] { TIMING_TEST_EXPECT_TRUE(g_triggerCallbackArg[0U].m_count == 1U); }); +TIMING_TEST_F(Listener_test, CallbackWithEventAndUserTypeIsCalledAfterNotify, Repeat(5), [&] { + m_sut.emplace(m_condVarData); + SimpleEventClass fuu; + uint64_t userType = 0U; + ASSERT_FALSE( + m_sut + ->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallbackWithUserType, userType) + .has_error()); + + fuu.triggerStoepsel(); + std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS)); + + TIMING_TEST_EXPECT_TRUE(g_triggerCallbackArg[0U].m_source == &fuu); + TIMING_TEST_EXPECT_TRUE(userType == 1U); +}); + +TIMING_TEST_F(Listener_test, CallbackWithUserTypeIsCalledAfterNotify, Repeat(5), [&] { + m_sut.emplace(m_condVarData); + SimpleEventClass fuu; + uint64_t userType = 0U; + ASSERT_FALSE(m_sut->attachEvent(fuu, Listener_test::triggerCallbackWithUserType, userType).has_error()); + + fuu.triggerNoEventType(); + std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS)); + + TIMING_TEST_EXPECT_TRUE(g_triggerCallbackArg[0U].m_source == &fuu); + TIMING_TEST_EXPECT_TRUE(userType == 1U); +}); + TIMING_TEST_F(Listener_test, CallbackIsCalledOnlyOnceWhenTriggered, Repeat(5), [&] { m_sut.emplace(m_condVarData); SimpleEventClass fuu1; From a0c4c6773fa46e94632ef71d2cc01f4ceca6fc15 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Mon, 12 Apr 2021 22:06:40 +0200 Subject: [PATCH 029/127] iox-#707 added listener as class member example which demonstrates additional callback member Signed-off-by: Christian Eltzschig --- iceoryx_examples/callbacks/CMakeLists.txt | 11 +- ...ice_callbacks_listener_as_class_member.cpp | 123 ++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp diff --git a/iceoryx_examples/callbacks/CMakeLists.txt b/iceoryx_examples/callbacks/CMakeLists.txt index 7c73cd1f11a..df76c9b4a07 100644 --- a/iceoryx_examples/callbacks/CMakeLists.txt +++ b/iceoryx_examples/callbacks/CMakeLists.txt @@ -37,7 +37,16 @@ target_link_libraries(iox-cpp-callbacks-subscriber ) target_compile_options(iox-cpp-callbacks-subscriber PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) +add_executable(iox-cpp-callbacks-listener-as-class-member ./ice_callbacks_listener_as_class_member.cpp) +target_link_libraries(iox-cpp-callbacks-listener-as-class-member + iceoryx_posh::iceoryx_posh +) +target_compile_options(iox-cpp-callbacks-listener-as-class-member PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) + + + set_target_properties( + iox-cpp-callbacks-listener-as-class-member iox-cpp-callbacks-subscriber iox-cpp-callbacks-publisher PROPERTIES @@ -47,6 +56,6 @@ set_target_properties( ) install( - TARGETS iox-cpp-callbacks-publisher iox-cpp-callbacks-subscriber + TARGETS iox-cpp-callbacks-publisher iox-cpp-callbacks-subscriber iox-cpp-callbacks-listener-as-class-member RUNTIME DESTINATION bin ) diff --git a/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp b/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp new file mode 100644 index 00000000000..dc458b464f0 --- /dev/null +++ b/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "iceoryx_posh/popo/listener.hpp" +#include "iceoryx_posh/popo/subscriber.hpp" +#include "iceoryx_posh/popo/user_trigger.hpp" +#include "iceoryx_posh/runtime/posh_runtime.hpp" +#include "iceoryx_utils/cxx/optional.hpp" +#include "iceoryx_utils/posix_wrapper/semaphore.hpp" +#include "iceoryx_utils/posix_wrapper/signal_handler.hpp" +#include "topic_data.hpp" + +#include +#include +#include + +std::atomic_bool keepRunning{true}; +constexpr char APP_NAME[] = "iox-cpp-callbacks-subscriber"; + +iox::posix::Semaphore shutdownSemaphore = + iox::posix::Semaphore::create(iox::posix::CreateUnnamedSingleProcessSemaphore, 0U).value(); + +static void sigHandler(int f_sig [[gnu::unused]]) +{ + shutdownSemaphore.post().or_else([](auto) { + std::cerr << "unable to call post on shutdownSemaphore - semaphore corrupt?" << std::endl; + std::terminate(); + }); + keepRunning = false; +} + +class CounterClass +{ + public: + CounterClass() + : m_subscriberLeft({"Radar", "FrontLeft", "Counter"}) + , m_subscriberRight({"Radar", "FrontRight", "Counter"}) + { + m_listener + .attachEvent(m_subscriberLeft, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback, *this) + .or_else([](auto) { + std::cerr << "unable to attach subscriberLeft" << std::endl; + std::terminate(); + }); + // it is possible to attach any callback here with the required signature. to simplify the + // example we attach the same callback onSampleReceivedCallback again + m_listener + .attachEvent(m_subscriberRight, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback, *this) + .or_else([](auto) { + std::cerr << "unable to attach subscriberRight" << std::endl; + std::terminate(); + }); + } + + void waitForControlC() noexcept + { + shutdownSemaphore.wait().or_else( + [](auto) { std::cerr << "unable to call wait on shutdownSemaphore - semaphore corrupt?" << std::endl; }); + } + + private: + static void onSampleReceivedCallback(iox::popo::Subscriber* subscriber, CounterClass* self) + { + subscriber->take().and_then([subscriber, self](auto& sample) { + auto instanceString = subscriber->getServiceDescription().getInstanceIDString(); + + // store the sample in the corresponding cache + if (instanceString == iox::capro::IdString_t("FrontLeft")) + { + self->m_leftCache.emplace(*sample); + } + else if (instanceString == iox::capro::IdString_t("FrontRight")) + { + self->m_rightCache.emplace(*sample); + } + + std::cout << "received: " << sample->counter << std::endl; + }); + + // if both caches are filled we can process them + if (self->m_leftCache && self->m_rightCache) + { + std::cout << "Received samples from FrontLeft and FrontRight. Sum of " << self->m_leftCache->counter + << " + " << self->m_rightCache->counter << " = " + << self->m_leftCache->counter + self->m_rightCache->counter << std::endl; + self->m_leftCache.reset(); + self->m_rightCache.reset(); + } + } + + iox::popo::Listener m_listener; + iox::popo::Subscriber m_subscriberLeft; + iox::popo::Subscriber m_subscriberRight; + iox::cxx::optional m_leftCache; + iox::cxx::optional m_rightCache; +}; + +int main() +{ + auto signalIntGuard = iox::posix::registerSignalHandler(iox::posix::Signal::INT, sigHandler); + auto signalTermGuard = iox::posix::registerSignalHandler(iox::posix::Signal::TERM, sigHandler); + + iox::runtime::PoshRuntime::initRuntime(APP_NAME); + + CounterClass counterClass; + + counterClass.waitForControlC(); + + return (EXIT_SUCCESS); +} From 9daf5ebf963c0a296a89222d6a2072d6becdc8d6 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Mon, 12 Apr 2021 22:32:27 +0200 Subject: [PATCH 030/127] iox-#707 documented the listener as class member example as well as the new listener methods Signed-off-by: Christian Eltzschig --- ...ice_callbacks_listener_as_class_member.cpp | 9 ++++- .../callbacks/ice_callbacks_subscriber.cpp | 8 +++- .../include/iceoryx_posh/popo/listener.hpp | 40 +++++++++++++++++-- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp b/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp index dc458b464f0..c85bf7c83b1 100644 --- a/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp +++ b/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp @@ -49,14 +49,16 @@ class CounterClass : m_subscriberLeft({"Radar", "FrontLeft", "Counter"}) , m_subscriberRight({"Radar", "FrontRight", "Counter"}) { + /// Attach the static method onSampleReceivedCallback and provide this as additional argument + /// to the callback to gain access to the object whenever the callback is called. + /// It is not possible to use a lambda with capturing here since they are not convertable to + /// a C function pointer. m_listener .attachEvent(m_subscriberLeft, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback, *this) .or_else([](auto) { std::cerr << "unable to attach subscriberLeft" << std::endl; std::terminate(); }); - // it is possible to attach any callback here with the required signature. to simplify the - // example we attach the same callback onSampleReceivedCallback again m_listener .attachEvent(m_subscriberRight, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback, *this) .or_else([](auto) { @@ -72,6 +74,9 @@ class CounterClass } private: + /// This method has to be static since only c functions are allowed as callback. + /// To gain access to the members and methods of CounterClass we provide as an additional argument the this pointer + /// which is stored in self static void onSampleReceivedCallback(iox::popo::Subscriber* subscriber, CounterClass* self) { subscriber->take().and_then([subscriber, self](auto& sample) { diff --git a/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp b/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp index 1f607f67361..cffffe2a134 100644 --- a/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp +++ b/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp @@ -107,13 +107,17 @@ int main() std::cerr << "unable to attach heartbeat event" << std::endl; std::terminate(); }); + + // It is possible to attach any c function here with a signature of void(iox::popo::Subscriber *). + // But please be aware that the listener does not take ownership of the callback, therefore it has to exist as + // long as the event is attached. Furthermore, it excludes lambdas which are capturing data since they are not + // convertable to a c function pointer. + // to simplify the example we attach the same callback onSampleReceivedCallback again listener.attachEvent(subscriberLeft, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback) .or_else([](auto) { std::cerr << "unable to attach subscriberLeft" << std::endl; std::terminate(); }); - // it is possible to attach any callback here with the required signature. to simplify the - // example we attach the same callback onSampleReceivedCallback again listener.attachEvent(subscriberRight, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback) .or_else([](auto) { std::cerr << "unable to attach subscriberRight" << std::endl; diff --git a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp index 1676c9377f8..ec5ff4a716a 100644 --- a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp +++ b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp @@ -67,18 +67,18 @@ class Listener public: using GenericCallbackPtr_t = void (*)(); using GenericCallbackRef_t = void (&)(); + using TranslationCallbackRef_t = void (&)(void* const, void* const, GenericCallbackPtr_t const); + using TranslationCallbackPtr_t = void (*)(void* const, void* const, GenericCallbackPtr_t const); template using CallbackRef_t = void (&)(T* const); template using CallbackWithUserTypeRef_t = void (&)(T* const, UserType* const); - using TranslationCallbackRef_t = void (&)(void* const, void* const, GenericCallbackPtr_t const); template using CallbackPtr_t = void (*)(T* const); template using CallbackWithUserTypePtr_t = void (*)(T* const, UserType* const); - using TranslationCallbackPtr_t = void (*)(void* const, void* const, GenericCallbackPtr_t const); Listener() noexcept; Listener(const Listener&) = delete; @@ -91,9 +91,13 @@ class Listener /// @brief Attaches an event. Hereby the event is defined as a class T, the eventOrigin and /// the corresponding callback which will be called when the event occurs. /// @note This method can be called from any thread concurrently without any restrictions! + /// @note The Listener does not take ownership of the eventCallback and only C function references are allowed. + /// This excludes lambdas with captures. If you would like to capture something please use the attachEvent + /// method with an additional user type. /// @tparam[in] T type of the class which will signal the event /// @param[in] eventOrigin the object which will signal the event (the origin) - /// @param[in] eventCallback callback which will be executed concurrently when the event occurs + /// @param[in] eventCallback callback which will be executed concurrently when the event occurs. The callback has + /// the signature: void(T*). /// @return If an error occurs the enum packed inside an expected which describes the error. template cxx::expected attachEvent(T& eventOrigin, CallbackRef_t eventCallback) noexcept; @@ -102,15 +106,32 @@ class Listener /// defines the event inside the class and the corresponding callback which will be called when the event /// occurs. /// @note This method can be called from any thread concurrently without any restrictions! + /// @note The Listener does not take ownership of the eventCallback and only C function references are allowed. + /// This excludes lambdas with captures. If you would like to capture something please use the attachEvent + /// method with an additional user type. /// @tparam[in] T type of the class which will signal the event /// @param[in] eventOrigin the object which will signal the event (the origin) /// @param[in] eventType enum required to specify the type of event inside of eventOrigin - /// @param[in] eventCallback callback which will be executed concurrently when the event occurs + /// @param[in] eventCallback callback which will be executed concurrently when the event occurs. The callback has + /// the signature: void(T*) /// @return If an error occurs the enum packed inside an expected which describes the error. template ::value>> cxx::expected attachEvent(T& eventOrigin, const EventType eventType, CallbackRef_t eventCallback) noexcept; + /// @brief Attaches an event. Hereby the event is defined as a class T, the eventOrigin, an enum which further + /// defines the event inside the class and the corresponding callback which will be called when the event + /// occurs. + /// @note This method can be called from any thread concurrently without any restrictions! + /// @note The Listener does not take ownership of the eventCallback and only C function references are allowed. + /// This excludes lambdas with captures. If you would like to capture something please use the userType. + /// @tparam[in] T type of the class which will signal the event + /// @param[in] eventOrigin the object which will signal the event (the origin) + /// @param[in] eventType enum required to specify the type of event inside of eventOrigin + /// @param[in] eventCallback callback which will be executed concurrently when the event occurs. The callback has + /// the signature: void(T*, UserType *) + /// @param[in] userType a reference to a user defined type which is provided to eventCallback as additional argument + /// @return If an error occurs the enum packed inside an expected which describes the error. template eventCallback, UserType& userType) noexcept; + /// @brief Attaches an event. Hereby the event is defined as a class T, the eventOrigin and + /// the corresponding callback which will be called when the event occurs. + /// @note This method can be called from any thread concurrently without any restrictions! + /// @note The Listener does not take ownership of the eventCallback and only C function references are allowed. + /// This excludes lambdas with captures. If you would like to capture something please use the userType. + /// @tparam[in] T type of the class which will signal the event + /// @param[in] eventOrigin the object which will signal the event (the origin) + /// @param[in] eventCallback callback which will be executed concurrently when the event occurs. The callback has + /// the signature: void(T*, UserType*) + /// @param[in] userType a reference to a user defined type which is provided to eventCallback as additional argument + /// @return If an error occurs the enum packed inside an expected which describes the error. template cxx::expected attachEvent(T& eventOrigin, CallbackWithUserTypeRef_t eventCallback, UserType& userType) noexcept; From 78c7411acc248cbfbfcb9d6ed36d4f1e2183a167 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 13 Apr 2021 01:41:50 +0200 Subject: [PATCH 031/127] iox-#707 implemented event callbacks and an event callback creator to user them without setting the template parameters explicitly Signed-off-by: Christian Eltzschig --- .../internal/popo/event_callback.inl | 39 ++++++++++ .../iceoryx_posh/internal/popo/listener.inl | 18 +++++ .../iceoryx_posh/popo/event_callback.hpp | 75 +++++++++++++++++++ .../include/iceoryx_posh/popo/listener.hpp | 5 ++ 4 files changed, 137 insertions(+) create mode 100644 iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl create mode 100644 iceoryx_posh/include/iceoryx_posh/popo/event_callback.hpp diff --git a/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl b/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl new file mode 100644 index 00000000000..8206e75742d --- /dev/null +++ b/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl @@ -0,0 +1,39 @@ +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef IOX_POSH_POPO_EVENT_CALLBACK_INL +#define IOX_POSH_POPO_EVENT_CALLBACK_INL + +namespace iox +{ +namespace popo +{ +template +inline EventCallback createEventCallback(void (&callback)(OriginType* const)) +{ + return EventCallback{&callback}; +} + +template +inline EventCallback createEventCallback(void (&callback)(OriginType* const, UserType* const), + UserType& userValue) +{ + return EventCallback{&callback, &userValue}; +} +} // namespace popo +} // namespace iox + +#endif diff --git a/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl b/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl index 3ba4976e436..0459d09b3fc 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl @@ -124,6 +124,24 @@ Listener::attachEvent(T& eventOrigin, CallbackWithUserTypeRef_t eve }); } +template +inline cxx::expected Listener::attachEvent(T& eventOrigin, + const EventCallback& eventCallback) noexcept +{ + return addEvent(&eventOrigin, + eventCallback.m_userValue, + static_cast(NoEnumUsed::PLACEHOLDER), + typeid(NoEnumUsed).hash_code(), + reinterpret_cast(*eventCallback.m_callback), + internal::TranslateAndCallTypelessCallback::call, + EventAttorney::getInvalidateTriggerMethod(eventOrigin)) + .and_then([&](auto& eventId) { + EventAttorney::enableEvent( + eventOrigin, TriggerHandle(*m_conditionVariableData, {*this, &Listener::removeTrigger}, eventId)); + }); +} + + template inline void Listener::detachEvent(T& eventOrigin, const EventType eventType) noexcept { diff --git a/iceoryx_posh/include/iceoryx_posh/popo/event_callback.hpp b/iceoryx_posh/include/iceoryx_posh/popo/event_callback.hpp new file mode 100644 index 00000000000..d64b600a062 --- /dev/null +++ b/iceoryx_posh/include/iceoryx_posh/popo/event_callback.hpp @@ -0,0 +1,75 @@ +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef IOX_POSH_POPO_EVENT_CALLBACK_HPP +#define IOX_POSH_POPO_EVENT_CALLBACK_HPP + +namespace iox +{ +namespace popo +{ +namespace internal +{ +struct NoType_t +{ +}; +} // namespace internal + +using GenericCallbackPtr_t = void (*)(); +using GenericCallbackRef_t = void (&)(); + +///@brief the struct describes a callback with a user defined type which can +/// be attached to a WaitSet or a Listener +template +struct EventCallback +{ + using Ref_t = void (&)(OriginType* const, UserType* const); + using Ptr_t = void (*)(OriginType* const, UserType* const); + + Ptr_t m_callback = nullptr; + UserType* m_userValue = nullptr; +}; + +///@brief the struct describes a callback which can be attached to a WaitSet or a Listener +template +struct EventCallback +{ + using Ref_t = void (&)(OriginType* const); + using Ptr_t = void (*)(OriginType* const); + + Ptr_t m_callback = nullptr; + internal::NoType_t* m_userValue = nullptr; +}; + +/// @brief creates an EventCallback +/// @param[in] callback reference to a callback with the signature void(OriginType*) +/// @return the callback stored inside of an EventCallback +template +EventCallback createEventCallback(void (&callback)(OriginType* const)); + +/// @brief creates an EventCallback with a user defined value +/// @param[in] callback reference to a callback with the signature void(OriginType*, UserType*) +/// @param[in] userValue reference to a user defined value +/// @return the callback and user value stored inside of an EventCallback +template +EventCallback createEventCallback(void (&callback)(OriginType* const, UserType* const), + UserType& userValue); +} // namespace popo +} // namespace iox + +#include "iceoryx_posh/internal/popo/event_callback.inl" + +#endif diff --git a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp index ec5ff4a716a..0a82907cb3d 100644 --- a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp +++ b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp @@ -20,6 +20,7 @@ #include "iceoryx_posh/internal/popo/building_blocks/condition_listener.hpp" #include "iceoryx_posh/popo/enum_trigger_type.hpp" #include "iceoryx_posh/popo/event_attorney.hpp" +#include "iceoryx_posh/popo/event_callback.hpp" #include "iceoryx_posh/popo/trigger_handle.hpp" #include "iceoryx_utils/cxx/expected.hpp" #include "iceoryx_utils/cxx/method_callback.hpp" @@ -156,6 +157,10 @@ class Listener cxx::expected attachEvent(T& eventOrigin, CallbackWithUserTypeRef_t eventCallback, UserType& userType) noexcept; + template + cxx::expected attachEvent(T& eventOrigin, const EventCallback& eventCallback) noexcept; + + /// @brief Detaches an event. Hereby, the event is defined as a class T, the eventOrigin and /// the eventType with further specifies the event inside of eventOrigin /// @note This method can be called from any thread concurrently without any restrictions! From 77dd3432a3d32cd178618a51a0d79274b0542bfe Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 13 Apr 2021 01:51:43 +0200 Subject: [PATCH 032/127] iox-#707 added EventCallback attachEvent variant for EventTypes Signed-off-by: Christian Eltzschig --- .../iceoryx_posh/internal/popo/listener.inl | 19 +++++++++++++++++++ .../include/iceoryx_posh/popo/listener.hpp | 7 +++++++ .../test/moduletests/test_popo_listener.cpp | 9 +++++---- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl b/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl index 0459d09b3fc..8feaae4bb59 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl @@ -141,6 +141,25 @@ inline cxx::expected Listener::attachEvent(T& eventOrigin, }); } +template +inline cxx::expected Listener::attachEvent(T& eventOrigin, + const EventType eventType, + const EventCallback& eventCallback) noexcept +{ + return addEvent(&eventOrigin, + eventCallback.m_userValue, + static_cast(eventType), + typeid(EventType).hash_code(), + reinterpret_cast(*eventCallback.m_callback), + internal::TranslateAndCallTypelessCallback::call, + EventAttorney::getInvalidateTriggerMethod(eventOrigin)) + .and_then([&](auto& eventId) { + EventAttorney::enableEvent( + eventOrigin, + TriggerHandle(*m_conditionVariableData, {*this, &Listener::removeTrigger}, eventId), + eventType); + }); +} template inline void Listener::detachEvent(T& eventOrigin, const EventType eventType) noexcept diff --git a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp index 0a82907cb3d..1b9b8e38f19 100644 --- a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp +++ b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp @@ -157,6 +157,13 @@ class Listener cxx::expected attachEvent(T& eventOrigin, CallbackWithUserTypeRef_t eventCallback, UserType& userType) noexcept; + template ::value>> + cxx::expected + attachEvent(T& eventOrigin, const EventType eventType, const EventCallback& eventCallback) noexcept; + template cxx::expected attachEvent(T& eventOrigin, const EventCallback& eventCallback) noexcept; diff --git a/iceoryx_posh/test/moduletests/test_popo_listener.cpp b/iceoryx_posh/test/moduletests/test_popo_listener.cpp index 532fa5a49c9..e16b2eadf6a 100644 --- a/iceoryx_posh/test/moduletests/test_popo_listener.cpp +++ b/iceoryx_posh/test/moduletests/test_popo_listener.cpp @@ -562,10 +562,11 @@ TIMING_TEST_F(Listener_test, CallbackWithEventAndUserTypeIsCalledAfterNotify, Re m_sut.emplace(m_condVarData); SimpleEventClass fuu; uint64_t userType = 0U; - ASSERT_FALSE( - m_sut - ->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallbackWithUserType, userType) - .has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallbackWithUserType, userType)) + .has_error()); fuu.triggerStoepsel(); std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS)); From cecb2a65c6f8cb3d3a80464a7b5a2995f7357b23 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 13 Apr 2021 02:04:42 +0200 Subject: [PATCH 033/127] iox-#707 removed the four old attachEvent variations and replaced them with the new two variations with the EventCallback abstraction Signed-off-by: Christian Eltzschig --- iceoryx_binding_c/source/c_listener.cpp | 5 +- ...ice_callbacks_listener_as_class_member.cpp | 8 +- .../callbacks/ice_callbacks_subscriber.cpp | 12 +- .../iceoryx_posh/internal/popo/listener.inl | 81 ------ .../iceoryx_posh/popo/event_callback.hpp | 3 - .../include/iceoryx_posh/popo/listener.hpp | 69 ----- .../test/moduletests/test_popo_listener.cpp | 257 ++++++++++++------ 7 files changed, 195 insertions(+), 240 deletions(-) diff --git a/iceoryx_binding_c/source/c_listener.cpp b/iceoryx_binding_c/source/c_listener.cpp index b3d248df11a..571dde377b4 100644 --- a/iceoryx_binding_c/source/c_listener.cpp +++ b/iceoryx_binding_c/source/c_listener.cpp @@ -48,7 +48,8 @@ ENUM iox_ListenerResult iox_listener_attach_subscriber_event(iox_listener_t cons const ENUM iox_SubscriberEvent subscriberEvent, void (*callback)(iox_sub_t)) { - auto result = self->attachEvent(*subscriber, c2cpp::subscriberEvent(subscriberEvent), *callback); + auto result = + self->attachEvent(*subscriber, c2cpp::subscriberEvent(subscriberEvent), createEventCallback(*callback)); if (result.has_error()) { return cpp2c::listenerResult(result.get_error()); @@ -60,7 +61,7 @@ ENUM iox_ListenerResult iox_listener_attach_user_trigger_event(iox_listener_t co iox_user_trigger_t const userTrigger, void (*callback)(iox_user_trigger_t)) { - auto result = self->attachEvent(*userTrigger, *callback); + auto result = self->attachEvent(*userTrigger, createEventCallback(*callback)); if (result.has_error()) { return cpp2c::listenerResult(result.get_error()); diff --git a/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp b/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp index c85bf7c83b1..36518e87d26 100644 --- a/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp +++ b/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp @@ -54,13 +54,17 @@ class CounterClass /// It is not possible to use a lambda with capturing here since they are not convertable to /// a C function pointer. m_listener - .attachEvent(m_subscriberLeft, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback, *this) + .attachEvent(m_subscriberLeft, + iox::popo::SubscriberEvent::DATA_RECEIVED, + iox::popo::createEventCallback(onSampleReceivedCallback, *this)) .or_else([](auto) { std::cerr << "unable to attach subscriberLeft" << std::endl; std::terminate(); }); m_listener - .attachEvent(m_subscriberRight, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback, *this) + .attachEvent(m_subscriberRight, + iox::popo::SubscriberEvent::DATA_RECEIVED, + iox::popo::createEventCallback(onSampleReceivedCallback, *this)) .or_else([](auto) { std::cerr << "unable to attach subscriberRight" << std::endl; std::terminate(); diff --git a/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp b/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp index cffffe2a134..33b9f57ac51 100644 --- a/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp +++ b/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp @@ -103,7 +103,7 @@ int main() }); // attach everything to the listener, from here on the callbacks are called when the corresponding event is occuring - listener.attachEvent(heartbeat, heartbeatCallback).or_else([](auto) { + listener.attachEvent(heartbeat, iox::popo::createEventCallback(heartbeatCallback)).or_else([](auto) { std::cerr << "unable to attach heartbeat event" << std::endl; std::terminate(); }); @@ -113,12 +113,18 @@ int main() // long as the event is attached. Furthermore, it excludes lambdas which are capturing data since they are not // convertable to a c function pointer. // to simplify the example we attach the same callback onSampleReceivedCallback again - listener.attachEvent(subscriberLeft, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback) + listener + .attachEvent(subscriberLeft, + iox::popo::SubscriberEvent::DATA_RECEIVED, + iox::popo::createEventCallback(onSampleReceivedCallback)) .or_else([](auto) { std::cerr << "unable to attach subscriberLeft" << std::endl; std::terminate(); }); - listener.attachEvent(subscriberRight, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback) + listener + .attachEvent(subscriberRight, + iox::popo::SubscriberEvent::DATA_RECEIVED, + iox::popo::createEventCallback(onSampleReceivedCallback)) .or_else([](auto) { std::cerr << "unable to attach subscriberRight" << std::endl; std::terminate(); diff --git a/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl b/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl index 8feaae4bb59..9a295788701 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl @@ -43,87 +43,6 @@ struct TranslateAndCallTypelessCallback }; } // namespace internal -template -inline cxx::expected Listener::attachEvent(T& eventOrigin, CallbackRef_t eventCallback) noexcept -{ - return addEvent(&eventOrigin, - nullptr, - static_cast(NoEnumUsed::PLACEHOLDER), - typeid(NoEnumUsed).hash_code(), - reinterpret_cast(eventCallback), - internal::TranslateAndCallTypelessCallback::call, - EventAttorney::getInvalidateTriggerMethod(eventOrigin)) - .and_then([&](auto& eventId) { - EventAttorney::enableEvent( - eventOrigin, TriggerHandle(*m_conditionVariableData, {*this, &Listener::removeTrigger}, eventId)); - }); -} - -template -inline cxx::expected -Listener::attachEvent(T& eventOrigin, const EventType eventType, CallbackRef_t eventCallback) noexcept -{ - static_assert(IS_EVENT_ENUM, - "Only enums with an underlying EventEnumIdentifier can be attached/detached to the Listener"); - return addEvent(&eventOrigin, - nullptr, - static_cast(eventType), - typeid(EventType).hash_code(), - reinterpret_cast(eventCallback), - internal::TranslateAndCallTypelessCallback::call, - EventAttorney::getInvalidateTriggerMethod(eventOrigin)) - .and_then([&](auto& eventId) { - EventAttorney::enableEvent( - eventOrigin, - TriggerHandle(*m_conditionVariableData, {*this, &Listener::removeTrigger}, eventId), - eventType); - }); -} - -template ::value>> -inline cxx::expected Listener::attachEvent(T& eventOrigin, - const EventType eventType, - CallbackWithUserTypeRef_t eventCallback, - UserType& userType) noexcept -{ - static_assert(IS_EVENT_ENUM, - "Only enums with an underlying EventEnumIdentifier can be attached/detached to the Listener"); - - return addEvent(&eventOrigin, - &userType, - static_cast(eventType), - typeid(EventType).hash_code(), - reinterpret_cast(eventCallback), - internal::TranslateAndCallTypelessCallback::call, - EventAttorney::getInvalidateTriggerMethod(eventOrigin)) - .and_then([&](auto& eventId) { - EventAttorney::enableEvent( - eventOrigin, - TriggerHandle(*m_conditionVariableData, {*this, &Listener::removeTrigger}, eventId), - eventType); - }); -} - -template -inline cxx::expected -Listener::attachEvent(T& eventOrigin, CallbackWithUserTypeRef_t eventCallback, UserType& userType) noexcept -{ - return addEvent(&eventOrigin, - &userType, - static_cast(NoEnumUsed::PLACEHOLDER), - typeid(NoEnumUsed).hash_code(), - reinterpret_cast(eventCallback), - internal::TranslateAndCallTypelessCallback::call, - EventAttorney::getInvalidateTriggerMethod(eventOrigin)) - .and_then([&](auto& eventId) { - EventAttorney::enableEvent( - eventOrigin, TriggerHandle(*m_conditionVariableData, {*this, &Listener::removeTrigger}, eventId)); - }); -} - template inline cxx::expected Listener::attachEvent(T& eventOrigin, const EventCallback& eventCallback) noexcept diff --git a/iceoryx_posh/include/iceoryx_posh/popo/event_callback.hpp b/iceoryx_posh/include/iceoryx_posh/popo/event_callback.hpp index d64b600a062..691e9347477 100644 --- a/iceoryx_posh/include/iceoryx_posh/popo/event_callback.hpp +++ b/iceoryx_posh/include/iceoryx_posh/popo/event_callback.hpp @@ -28,9 +28,6 @@ struct NoType_t }; } // namespace internal -using GenericCallbackPtr_t = void (*)(); -using GenericCallbackRef_t = void (&)(); - ///@brief the struct describes a callback with a user defined type which can /// be attached to a WaitSet or a Listener template diff --git a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp index 1b9b8e38f19..cbd49d1c1bc 100644 --- a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp +++ b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp @@ -89,74 +89,6 @@ class Listener Listener& operator=(const Listener&) = delete; Listener& operator=(Listener&&) = delete; - /// @brief Attaches an event. Hereby the event is defined as a class T, the eventOrigin and - /// the corresponding callback which will be called when the event occurs. - /// @note This method can be called from any thread concurrently without any restrictions! - /// @note The Listener does not take ownership of the eventCallback and only C function references are allowed. - /// This excludes lambdas with captures. If you would like to capture something please use the attachEvent - /// method with an additional user type. - /// @tparam[in] T type of the class which will signal the event - /// @param[in] eventOrigin the object which will signal the event (the origin) - /// @param[in] eventCallback callback which will be executed concurrently when the event occurs. The callback has - /// the signature: void(T*). - /// @return If an error occurs the enum packed inside an expected which describes the error. - template - cxx::expected attachEvent(T& eventOrigin, CallbackRef_t eventCallback) noexcept; - - /// @brief Attaches an event. Hereby the event is defined as a class T, the eventOrigin, an enum which further - /// defines the event inside the class and the corresponding callback which will be called when the event - /// occurs. - /// @note This method can be called from any thread concurrently without any restrictions! - /// @note The Listener does not take ownership of the eventCallback and only C function references are allowed. - /// This excludes lambdas with captures. If you would like to capture something please use the attachEvent - /// method with an additional user type. - /// @tparam[in] T type of the class which will signal the event - /// @param[in] eventOrigin the object which will signal the event (the origin) - /// @param[in] eventType enum required to specify the type of event inside of eventOrigin - /// @param[in] eventCallback callback which will be executed concurrently when the event occurs. The callback has - /// the signature: void(T*) - /// @return If an error occurs the enum packed inside an expected which describes the error. - template ::value>> - cxx::expected - attachEvent(T& eventOrigin, const EventType eventType, CallbackRef_t eventCallback) noexcept; - - /// @brief Attaches an event. Hereby the event is defined as a class T, the eventOrigin, an enum which further - /// defines the event inside the class and the corresponding callback which will be called when the event - /// occurs. - /// @note This method can be called from any thread concurrently without any restrictions! - /// @note The Listener does not take ownership of the eventCallback and only C function references are allowed. - /// This excludes lambdas with captures. If you would like to capture something please use the userType. - /// @tparam[in] T type of the class which will signal the event - /// @param[in] eventOrigin the object which will signal the event (the origin) - /// @param[in] eventType enum required to specify the type of event inside of eventOrigin - /// @param[in] eventCallback callback which will be executed concurrently when the event occurs. The callback has - /// the signature: void(T*, UserType *) - /// @param[in] userType a reference to a user defined type which is provided to eventCallback as additional argument - /// @return If an error occurs the enum packed inside an expected which describes the error. - template ::value>> - cxx::expected attachEvent(T& eventOrigin, - const EventType eventType, - CallbackWithUserTypeRef_t eventCallback, - UserType& userType) noexcept; - - /// @brief Attaches an event. Hereby the event is defined as a class T, the eventOrigin and - /// the corresponding callback which will be called when the event occurs. - /// @note This method can be called from any thread concurrently without any restrictions! - /// @note The Listener does not take ownership of the eventCallback and only C function references are allowed. - /// This excludes lambdas with captures. If you would like to capture something please use the userType. - /// @tparam[in] T type of the class which will signal the event - /// @param[in] eventOrigin the object which will signal the event (the origin) - /// @param[in] eventCallback callback which will be executed concurrently when the event occurs. The callback has - /// the signature: void(T*, UserType*) - /// @param[in] userType a reference to a user defined type which is provided to eventCallback as additional argument - /// @return If an error occurs the enum packed inside an expected which describes the error. - template - cxx::expected - attachEvent(T& eventOrigin, CallbackWithUserTypeRef_t eventCallback, UserType& userType) noexcept; - template cxx::expected attachEvent(T& eventOrigin, const EventCallback& eventCallback) noexcept; - /// @brief Detaches an event. Hereby, the event is defined as a class T, the eventOrigin and /// the eventType with further specifies the event inside of eventOrigin /// @note This method can be called from any thread concurrently without any restrictions! diff --git a/iceoryx_posh/test/moduletests/test_popo_listener.cpp b/iceoryx_posh/test/moduletests/test_popo_listener.cpp index e16b2eadf6a..0e217909918 100644 --- a/iceoryx_posh/test/moduletests/test_popo_listener.cpp +++ b/iceoryx_posh/test/moduletests/test_popo_listener.cpp @@ -182,8 +182,11 @@ class Listener_test : public Test { for (auto& e : g_toBeAttached.GetCopy()) { - ASSERT_FALSE( - e.sut->attachEvent(*e.object, SimpleEvent::StoepselBachelorParty, triggerCallback<0U>).has_error()); + ASSERT_FALSE(e.sut + ->attachEvent(*e.object, + SimpleEvent::StoepselBachelorParty, + createEventCallback(triggerCallback<0U>)) + .has_error()); } } @@ -239,7 +242,8 @@ class Listener_test : public Test { for (uint64_t i = 0U; i < m_sut->capacity(); ++i) { - EXPECT_FALSE(m_sut->attachEvent(m_simpleEvents[i], Listener_test::triggerCallback<0U>).has_error()); + EXPECT_FALSE(m_sut->attachEvent(m_simpleEvents[i], createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); EXPECT_THAT(m_sut->size(), Eq(i + 1U)); } } @@ -248,7 +252,9 @@ class Listener_test : public Test for (uint64_t i = 0U; i < m_sut->capacity(); ++i) { bool hasError = - m_sut->attachEvent(m_simpleEvents[i], eventType, Listener_test::triggerCallback<0U>).has_error(); + m_sut + ->attachEvent(m_simpleEvents[i], eventType, createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error(); EXPECT_FALSE(hasError); if (hasError) { @@ -284,7 +290,9 @@ struct AttachEvent template static void doIt(TestListener& sut, std::vector& events, const EventType event) { - EXPECT_THAT(sut.attachEvent(events[N], event, Listener_test::triggerCallback).has_error(), Eq(false)); + EXPECT_THAT( + sut.attachEvent(events[N], event, createEventCallback(Listener_test::triggerCallback)).has_error(), + Eq(false)); AttachEvent::doIt(sut, events, event); } }; @@ -295,7 +303,9 @@ struct AttachEvent<0U> template static void doIt(TestListener& sut, std::vector& events, const EventType event) { - EXPECT_THAT(sut.attachEvent(events[0U], event, Listener_test::triggerCallback<0U>).has_error(), Eq(false)); + EXPECT_THAT( + sut.attachEvent(events[0U], event, createEventCallback(Listener_test::triggerCallback<0U>)).has_error(), + Eq(false)); } }; } // namespace @@ -316,7 +326,8 @@ TEST_F(Listener_test, IsEmptyWhenConstructed) TEST_F(Listener_test, AttachingWithoutEnumIfEnoughSpaceAvailableWorks) { - EXPECT_FALSE(m_sut->attachEvent(m_simpleEvents[0U], Listener_test::triggerCallback<0U>).has_error()); + EXPECT_FALSE( + m_sut->attachEvent(m_simpleEvents[0U], createEventCallback(Listener_test::triggerCallback<0U>)).has_error()); EXPECT_THAT(m_sut->size(), Eq(1U)); } @@ -336,7 +347,8 @@ TEST_F(Listener_test, DetachDecreasesSize) TEST_F(Listener_test, AttachWithoutEnumOneMoreThanCapacityFails) { fillUpWithSimpleEvents(); - auto result = m_sut->attachEvent(m_simpleEvents[m_sut->capacity()], Listener_test::triggerCallback<0U>); + auto result = + m_sut->attachEvent(m_simpleEvents[m_sut->capacity()], createEventCallback(Listener_test::triggerCallback<0U>)); ASSERT_TRUE(result.has_error()); EXPECT_THAT(result.get_error(), Eq(ListenerError::LISTENER_FULL)); @@ -344,8 +356,11 @@ TEST_F(Listener_test, AttachWithoutEnumOneMoreThanCapacityFails) TEST_F(Listener_test, AttachingWithEnumIfEnoughSpaceAvailableWorks) { - EXPECT_FALSE( - m_sut->attachEvent(m_simpleEvents[0U], SimpleEvent::Hypnotoad, Listener_test::triggerCallback<0U>).has_error()); + EXPECT_FALSE(m_sut + ->attachEvent(m_simpleEvents[0U], + SimpleEvent::Hypnotoad, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); EXPECT_THAT(m_sut->size(), Eq(1U)); } @@ -357,8 +372,9 @@ TEST_F(Listener_test, AttachWithEnumTillCapacityIsFullWorks) TEST_F(Listener_test, AttachWithEnumOneMoreThanCapacityFails) { fillUpWithSimpleEventsWithEnum(SimpleEvent::Hypnotoad); - auto result = m_sut->attachEvent( - m_simpleEvents[m_sut->capacity()], SimpleEvent::Hypnotoad, Listener_test::triggerCallback<0U>); + auto result = m_sut->attachEvent(m_simpleEvents[m_sut->capacity()], + SimpleEvent::Hypnotoad, + createEventCallback(Listener_test::triggerCallback<0U>)); ASSERT_TRUE(result.has_error()); EXPECT_THAT(result.get_error(), Eq(ListenerError::LISTENER_FULL)); @@ -369,10 +385,11 @@ TEST_F(Listener_test, DetachMakesSpaceForAnotherAttachWithEventEnum) fillUpWithSimpleEventsWithEnum(SimpleEvent::Hypnotoad); m_sut->detachEvent(m_simpleEvents[0U], SimpleEvent::Hypnotoad); - EXPECT_FALSE( - m_sut - ->attachEvent(m_simpleEvents[m_sut->capacity()], SimpleEvent::Hypnotoad, Listener_test::triggerCallback<0U>) - .has_error()); + EXPECT_FALSE(m_sut + ->attachEvent(m_simpleEvents[m_sut->capacity()], + SimpleEvent::Hypnotoad, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); } TEST_F(Listener_test, DetachMakesSpaceForAnotherAttachWithoutEventEnum) @@ -380,58 +397,75 @@ TEST_F(Listener_test, DetachMakesSpaceForAnotherAttachWithoutEventEnum) fillUpWithSimpleEvents(); m_sut->detachEvent(m_simpleEvents[0U]); - EXPECT_FALSE(m_sut->attachEvent(m_simpleEvents[m_sut->capacity()], Listener_test::triggerCallback<0U>).has_error()); + EXPECT_FALSE( + m_sut->attachEvent(m_simpleEvents[m_sut->capacity()], createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); } TEST_F(Listener_test, AttachingEventWithoutEventTypeLeadsToAttachedNoEventEnumTriggerHandle) { - ASSERT_FALSE(m_sut->attachEvent(m_simpleEvents[0U], Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE( + m_sut->attachEvent(m_simpleEvents[0U], createEventCallback(Listener_test::triggerCallback<0U>)).has_error()); EXPECT_TRUE(m_simpleEvents[0U].m_handleNoEventEnum.isValid()); } TEST_F(Listener_test, AttachingEventWithEventTypeLeadsToAttachedTriggerHandle) { - ASSERT_FALSE( - m_sut->attachEvent(m_simpleEvents[0U], SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>) - .has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(m_simpleEvents[0U], + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); EXPECT_TRUE(m_simpleEvents[0U].m_handleStoepsel.isValid()); } TEST_F(Listener_test, OverridingAlreadyAttachedEventWithEnumFails) { - ASSERT_FALSE( - m_sut->attachEvent(m_simpleEvents[0U], SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>) - .has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(m_simpleEvents[0U], + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); - auto result = - m_sut->attachEvent(m_simpleEvents[0U], SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>); + auto result = m_sut->attachEvent(m_simpleEvents[0U], + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)); ASSERT_TRUE(result.has_error()); EXPECT_THAT(result.get_error(), Eq(ListenerError::EVENT_ALREADY_ATTACHED)); } TEST_F(Listener_test, OverridingAlreadyAttachedEventWithoutEnumFails) { - ASSERT_FALSE(m_sut->attachEvent(m_simpleEvents[0U], Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE( + m_sut->attachEvent(m_simpleEvents[0U], createEventCallback(Listener_test::triggerCallback<0U>)).has_error()); - auto result = m_sut->attachEvent(m_simpleEvents[0U], Listener_test::triggerCallback<0U>); + auto result = m_sut->attachEvent(m_simpleEvents[0U], createEventCallback(Listener_test::triggerCallback<0U>)); ASSERT_TRUE(result.has_error()); EXPECT_THAT(result.get_error(), Eq(ListenerError::EVENT_ALREADY_ATTACHED)); } TEST_F(Listener_test, AttachingSameClassWithTwoDifferentEventsWorks) { - ASSERT_FALSE( - m_sut->attachEvent(m_simpleEvents[0U], SimpleEvent::Hypnotoad, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(m_simpleEvents[0U], + SimpleEvent::Hypnotoad, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); - EXPECT_FALSE( - m_sut->attachEvent(m_simpleEvents[0U], SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>) - .has_error()); + EXPECT_FALSE(m_sut + ->attachEvent(m_simpleEvents[0U], + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); } TEST_F(Listener_test, DetachingSameClassWithDifferentEventEnumChangesNothing) { - ASSERT_FALSE( - m_sut->attachEvent(m_simpleEvents[0U], SimpleEvent::Hypnotoad, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(m_simpleEvents[0U], + SimpleEvent::Hypnotoad, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); m_sut->detachEvent(m_simpleEvents[0U], SimpleEvent::StoepselBachelorParty); EXPECT_THAT(m_sut->size(), Eq(1U)); @@ -439,8 +473,11 @@ TEST_F(Listener_test, DetachingSameClassWithDifferentEventEnumChangesNothing) TEST_F(Listener_test, DetachingDifferentClassWithSameEventEnumChangesNothing) { - ASSERT_FALSE( - m_sut->attachEvent(m_simpleEvents[0U], SimpleEvent::Hypnotoad, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(m_simpleEvents[0U], + SimpleEvent::Hypnotoad, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); m_sut->detachEvent(m_simpleEvents[1U], SimpleEvent::Hypnotoad); EXPECT_THAT(m_sut->size(), Eq(1U)); @@ -486,7 +523,7 @@ TEST_F(Listener_test, AttachedEventDTorDetachesItself) { { SimpleEventClass fuu; - ASSERT_FALSE(m_sut->attachEvent(fuu, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut->attachEvent(fuu, createEventCallback(Listener_test::triggerCallback<0U>)).has_error()); } EXPECT_THAT(m_sut->size(), Eq(0U)); @@ -495,7 +532,7 @@ TEST_F(Listener_test, AttachedEventDTorDetachesItself) TEST_F(Listener_test, AttachingSimpleEventWithoutEnumSetsNoEventEnumTriggerHandle) { SimpleEventClass fuu; - ASSERT_FALSE(m_sut->attachEvent(fuu, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut->attachEvent(fuu, createEventCallback(Listener_test::triggerCallback<0U>)).has_error()); EXPECT_TRUE(static_cast(fuu.m_handleNoEventEnum)); } @@ -503,7 +540,7 @@ TEST_F(Listener_test, AttachingSimpleEventWithoutEnumSetsNoEventEnumTriggerHandl TEST_F(Listener_test, DetachingSimpleEventResetsTriggerHandle) { SimpleEventClass fuu; - ASSERT_FALSE(m_sut->attachEvent(fuu, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut->attachEvent(fuu, createEventCallback(Listener_test::triggerCallback<0U>)).has_error()); m_sut->detachEvent(fuu); EXPECT_FALSE(static_cast(fuu.m_handleNoEventEnum)); @@ -512,8 +549,11 @@ TEST_F(Listener_test, DetachingSimpleEventResetsTriggerHandle) TEST_F(Listener_test, AttachingEventWithEnumSetsTriggerHandle) { SimpleEventClass fuu; - ASSERT_FALSE( - m_sut->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); EXPECT_TRUE(static_cast(fuu.m_handleStoepsel)); } @@ -521,8 +561,11 @@ TEST_F(Listener_test, AttachingEventWithEnumSetsTriggerHandle) TEST_F(Listener_test, DetachingEventWithEnumResetsTriggerHandle) { SimpleEventClass fuu; - ASSERT_FALSE( - m_sut->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); m_sut->detachEvent(fuu, SimpleEvent::StoepselBachelorParty); EXPECT_FALSE(static_cast(fuu.m_handleStoepsel)); @@ -531,8 +574,11 @@ TEST_F(Listener_test, DetachingEventWithEnumResetsTriggerHandle) TEST_F(Listener_test, DetachingNonAttachedEventResetsNothing) { SimpleEventClass fuu; - ASSERT_FALSE( - m_sut->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); m_sut->detachEvent(fuu, SimpleEvent::Hypnotoad); EXPECT_TRUE(static_cast(fuu.m_handleStoepsel)); @@ -548,8 +594,11 @@ TEST_F(Listener_test, DetachingNonAttachedEventResetsNothing) TIMING_TEST_F(Listener_test, CallbackIsCalledAfterNotify, Repeat(5), [&] { m_sut.emplace(m_condVarData); SimpleEventClass fuu; - ASSERT_FALSE( - m_sut->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); fuu.triggerStoepsel(); std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS)); @@ -579,7 +628,8 @@ TIMING_TEST_F(Listener_test, CallbackWithUserTypeIsCalledAfterNotify, Repeat(5), m_sut.emplace(m_condVarData); SimpleEventClass fuu; uint64_t userType = 0U; - ASSERT_FALSE(m_sut->attachEvent(fuu, Listener_test::triggerCallbackWithUserType, userType).has_error()); + ASSERT_FALSE( + m_sut->attachEvent(fuu, createEventCallback(Listener_test::triggerCallbackWithUserType, userType)).has_error()); fuu.triggerNoEventType(); std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS)); @@ -592,10 +642,16 @@ TIMING_TEST_F(Listener_test, CallbackIsCalledOnlyOnceWhenTriggered, Repeat(5), [ m_sut.emplace(m_condVarData); SimpleEventClass fuu1; SimpleEventClass fuu2; - ASSERT_FALSE( - m_sut->attachEvent(fuu1, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>).has_error()); - ASSERT_FALSE( - m_sut->attachEvent(fuu2, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<1U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu1, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu2, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<1U>)) + .has_error()); fuu1.triggerStoepsel(); std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS)); @@ -611,8 +667,11 @@ TIMING_TEST_F(Listener_test, CallbackIsCalledOnlyOnceWhenTriggered, Repeat(5), [ TIMING_TEST_F(Listener_test, TriggerWhileInCallbackLeadsToAnotherCallback, Repeat(5), [&] { m_sut.emplace(m_condVarData); SimpleEventClass fuu; - ASSERT_FALSE( - m_sut->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); constexpr uint64_t NUMBER_OF_TRIGGER_UNBLOCKS = 10U; @@ -633,10 +692,16 @@ TIMING_TEST_F(Listener_test, TriggerWhileInCallbackLeadsToAnotherCallbackOnce, R m_sut.emplace(m_condVarData); SimpleEventClass fuu; SimpleEventClass bar; - ASSERT_FALSE( - m_sut->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>).has_error()); - ASSERT_FALSE( - m_sut->attachEvent(bar, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<1U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(bar, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<1U>)) + .has_error()); constexpr uint64_t NUMBER_OF_TRIGGER_UNBLOCKS = 10U; @@ -659,8 +724,11 @@ TIMING_TEST_F(Listener_test, TriggerWhileInCallbackLeadsToAnotherCallbackOnce, R TIMING_TEST_F(Listener_test, TriggerMultipleTimesWhileInCallbackLeadsToAnotherCallback, Repeat(5), [&] { m_sut.emplace(m_condVarData); SimpleEventClass fuu; - ASSERT_FALSE( - m_sut->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); constexpr uint64_t NUMBER_OF_RETRIGGERS = 10U; @@ -684,10 +752,16 @@ TIMING_TEST_F(Listener_test, TriggerMultipleTimesWhileInCallbackLeadsToAnotherCa m_sut.emplace(m_condVarData); SimpleEventClass fuu; SimpleEventClass bar; - ASSERT_FALSE( - m_sut->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>).has_error()); - ASSERT_FALSE( - m_sut->attachEvent(bar, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<1U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(bar, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<1U>)) + .has_error()); constexpr uint64_t NUMBER_OF_RETRIGGERS = 10U; @@ -713,8 +787,11 @@ TIMING_TEST_F(Listener_test, TriggerMultipleTimesWhileInCallbackLeadsToAnotherCa TIMING_TEST_F(Listener_test, NoTriggerLeadsToNoCallback, Repeat(5), [&] { m_sut.emplace(m_condVarData); SimpleEventClass fuu; - ASSERT_FALSE( - m_sut->attachEvent(fuu, SimpleEvent::StoepselBachelorParty, Listener_test::triggerCallback<0U>).has_error()); + ASSERT_FALSE(m_sut + ->attachEvent(fuu, + SimpleEvent::StoepselBachelorParty, + createEventCallback(Listener_test::triggerCallback<0U>)) + .has_error()); std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS)); @@ -793,13 +870,17 @@ TIMING_TEST_F(Listener_test, AttachingWhileCallbackIsRunningWorks, Repeat(5), [& m_sut.emplace(m_condVarData); std::vector events(iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER); - ASSERT_FALSE(m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, triggerCallback<0U>).has_error()); + ASSERT_FALSE( + m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, createEventCallback(triggerCallback<0U>)) + .has_error()); g_triggerCallbackRuntimeInMs = 3U * CALLBACK_WAIT_IN_MS / 2U; events[0U].triggerStoepsel(); std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS)); - ASSERT_FALSE(m_sut->attachEvent(events[1U], SimpleEvent::StoepselBachelorParty, triggerCallback<1U>).has_error()); + ASSERT_FALSE( + m_sut->attachEvent(events[1U], SimpleEvent::StoepselBachelorParty, createEventCallback(triggerCallback<1U>)) + .has_error()); events[1U].triggerStoepsel(); std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS * 2U)); @@ -814,7 +895,7 @@ TIMING_TEST_F(Listener_test, AttachingMultipleWhileCallbackIsRunningWorks, Repea ASSERT_FALSE(m_sut ->attachEvent(events[iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER - 1U], SimpleEvent::StoepselBachelorParty, - triggerCallback) + createEventCallback(triggerCallback)) .has_error()); g_triggerCallbackRuntimeInMs = 3U * CALLBACK_WAIT_IN_MS / 2U; @@ -841,7 +922,9 @@ TIMING_TEST_F(Listener_test, DetachingWhileCallbackIsRunningWorks, Repeat(5), [& m_sut.emplace(m_condVarData); std::vector events(iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER); - ASSERT_FALSE(m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, triggerCallback<0U>).has_error()); + ASSERT_FALSE( + m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, createEventCallback(triggerCallback<0U>)) + .has_error()); g_triggerCallbackRuntimeInMs = 3U * CALLBACK_WAIT_IN_MS / 2U; events[0U].triggerStoepsel(); @@ -859,7 +942,9 @@ TIMING_TEST_F(Listener_test, DetachingWhileCallbackIsRunningWorks, Repeat(5), [& TIMING_TEST_F(Listener_test, DetachingWhileCallbackIsRunningBlocksDetach, Repeat(5), [&] { m_sut.emplace(m_condVarData); std::vector events(iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER); - ASSERT_FALSE(m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, triggerCallback<0U>).has_error()); + ASSERT_FALSE( + m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, createEventCallback(triggerCallback<0U>)) + .has_error()); g_triggerCallbackRuntimeInMs = 3U * CALLBACK_WAIT_IN_MS / 2U; events[0U].triggerStoepsel(); std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS / 4U)); @@ -875,7 +960,9 @@ TIMING_TEST_F(Listener_test, DetachingWhileCallbackIsRunningBlocksDetach, Repeat TIMING_TEST_F(Listener_test, EventDestructorBlocksWhenCallbackIsRunning, Repeat(5), [&] { m_sut.emplace(m_condVarData); SimpleEventClass* event = new SimpleEventClass(); - ASSERT_FALSE(m_sut->attachEvent(*event, SimpleEvent::StoepselBachelorParty, triggerCallback<0U>).has_error()); + ASSERT_FALSE( + m_sut->attachEvent(*event, SimpleEvent::StoepselBachelorParty, createEventCallback(triggerCallback<0U>)) + .has_error()); g_triggerCallbackRuntimeInMs = 3U * CALLBACK_WAIT_IN_MS / 2U; event->triggerStoepsel(); std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS / 4U)); @@ -927,7 +1014,7 @@ TIMING_TEST_F(Listener_test, AttachingDetachingRunsIndependentOfCallback, Repeat ASSERT_FALSE(m_sut ->attachEvent(events[iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER - 1U], SimpleEvent::StoepselBachelorParty, - triggerCallback) + createEventCallback(triggerCallback)) .has_error()); g_triggerCallbackRuntimeInMs = 3U * CALLBACK_WAIT_IN_MS / 2U; events[iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER - 1U].triggerStoepsel(); @@ -957,7 +1044,8 @@ TIMING_TEST_F(Listener_test, DetachingSelfInCallbackWorks, Repeat(5), [&] { std::vector events(iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER); g_toBeDetached->push_back({&events[0U], &*m_sut}); - ASSERT_FALSE(m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, detachCallback).has_error()); + ASSERT_FALSE(m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, createEventCallback(detachCallback)) + .has_error()); events[0U].triggerStoepsel(); @@ -972,8 +1060,11 @@ TIMING_TEST_F(Listener_test, DetachingNonSelfEventInCallbackWorks, Repeat(5), [& std::vector events(iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER); g_toBeDetached->push_back({&events[1U], &*m_sut}); - ASSERT_FALSE(m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, detachCallback).has_error()); - ASSERT_FALSE(m_sut->attachEvent(events[1U], SimpleEvent::StoepselBachelorParty, triggerCallback<1U>).has_error()); + ASSERT_FALSE(m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, createEventCallback(detachCallback)) + .has_error()); + ASSERT_FALSE( + m_sut->attachEvent(events[1U], SimpleEvent::StoepselBachelorParty, createEventCallback(triggerCallback<1U>)) + .has_error()); events[0U].triggerStoepsel(); @@ -998,9 +1089,14 @@ TIMING_TEST_F(Listener_test, DetachedCallbacksAreNotBeingCalledWhenTriggeredBefo std::vector events(iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER); g_toBeDetached->push_back({&events[1U], &*m_sut}); - ASSERT_FALSE(m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, notifyAndThenDetachStoepselCallback) + ASSERT_FALSE(m_sut + ->attachEvent(events[0U], + SimpleEvent::StoepselBachelorParty, + createEventCallback(notifyAndThenDetachStoepselCallback)) .has_error()); - ASSERT_FALSE(m_sut->attachEvent(events[1U], SimpleEvent::StoepselBachelorParty, triggerCallback<1U>).has_error()); + ASSERT_FALSE( + m_sut->attachEvent(events[1U], SimpleEvent::StoepselBachelorParty, createEventCallback(triggerCallback<1U>)) + .has_error()); g_triggerCallbackRuntimeInMs = 3U * CALLBACK_WAIT_IN_MS / 2U; events[1U].triggerStoepsel(); @@ -1025,7 +1121,8 @@ TIMING_TEST_F(Listener_test, AttachingInCallbackWorks, Repeat(5), [&] { std::vector events(iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER); g_toBeAttached->push_back({&events[1U], &*m_sut}); - ASSERT_FALSE(m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, attachCallback).has_error()); + ASSERT_FALSE(m_sut->attachEvent(events[0U], SimpleEvent::StoepselBachelorParty, createEventCallback(attachCallback)) + .has_error()); events[0U].triggerStoepsel(); std::this_thread::sleep_for(std::chrono::milliseconds(CALLBACK_WAIT_IN_MS / 2U)); From 6775cafa30d198aebce4deda6a3a7f97c566c52e Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 13 Apr 2021 02:44:33 +0200 Subject: [PATCH 034/127] iox-#707 added listener as member example to the readme Signed-off-by: Christian Eltzschig --- iceoryx_examples/callbacks/README.md | 131 +++++++++++++++++- ...ice_callbacks_listener_as_class_member.cpp | 2 +- .../include/iceoryx_posh/popo/listener.hpp | 18 +++ 3 files changed, 143 insertions(+), 8 deletions(-) diff --git a/iceoryx_examples/callbacks/README.md b/iceoryx_examples/callbacks/README.md index 9b10b4752cd..71cd6b22bd4 100644 --- a/iceoryx_examples/callbacks/README.md +++ b/iceoryx_examples/callbacks/README.md @@ -86,18 +86,28 @@ our `heartbeat` user trigger to print the heartbeat message to the console via a callback (`heartbeatCallback`). ```cpp -listener.attachEvent(heartbeat, heartbeatCallback).or_else([](auto) { - std::cerr << "unable to attach heartbeat event" << std::endl; - std::terminate(); +listener.attachEvent(heartbeat, iox::popo::createEventCallback(heartbeatCallback)) + .or_else([](auto) { + std::cerr << "unable to attach heartbeat event" << std::endl; + std::terminate(); }); -listener.attachEvent(subscriberLeft, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback) + +listener.attachEvent(subscriberLeft, + iox::popo::SubscriberEvent::DATA_RECEIVED, + iox::popo::createEventCallback(onSampleReceivedCallback)) .or_else([](auto) { std::cerr << "unable to attach subscriberLeft" << std::endl; std::terminate(); }); -// it is possible to attach any callback here with the required signature. to simplify the -// example we attach the same callback onSampleReceivedCallback again -listener.attachEvent(subscriberRight, iox::popo::SubscriberEvent::DATA_RECEIVED, onSampleReceivedCallback) + +// It is possible to attach any c function here with a signature of void(iox::popo::Subscriber *). +// But please be aware that the listener does not take ownership of the callback, therefore it has to exist as +// long as the event is attached. Furthermore, it excludes lambdas which are capturing data since they are not +// convertable to a c function pointer. +// to simplify the example we attach the same callback onSampleReceivedCallback again +listener.attachEvent(subscriberRight, + iox::popo::SubscriberEvent::DATA_RECEIVED, + iox::popo::createEventCallback(onSampleReceivedCallback)) .or_else([](auto) { std::cerr << "unable to attach subscriberRight" << std::endl; std::terminate(); @@ -184,6 +194,113 @@ Afterward, we reset both caches to start fresh again. } ``` +### ice_callbacks_listener_as_class_member.cpp + +This example is identical to the [ice_callbacks_subscriber.cpp](#ice_callbacks_subscriber.cpp) +one except that we left out the cyclic heartbeat trigger. The key difference is that +the listener is now a class member and in every callback we would like to change +some member variables. To do this we require an additional pointer to the object +since the listener requires c function references which do not allow the usage +of lambdas with capturing. Here we can use the userType feature which allows us +to provide the this pointer as additional argument to the callback. + +The main function is now pretty short, we instantiate our object of type `CounterClass` +and call `waitForControlC` which uses the `shutdownSemaphore` like in the +previous example to wait for the control c event from the user. +```cpp +auto signalIntGuard = iox::posix::registerSignalHandler(iox::posix::Signal::INT, sigHandler); +auto signalTermGuard = iox::posix::registerSignalHandler(iox::posix::Signal::TERM, sigHandler); + +iox::runtime::PoshRuntime::initRuntime(APP_NAME); + +CounterClass counterClass; + +counterClass.waitForControlC(); +``` + +Our `CounterClass` has the following members: +```cpp +class CounterClass { + //... + iox::popo::Subscriber m_subscriberLeft; + iox::popo::Subscriber m_subscriberRight; + iox::cxx::optional m_leftCache; + iox::cxx::optional m_rightCache; + iox::popo::Listener m_listener; +}; +``` + +And their purposes are the same as in the previous example. In the constructor +we initialize the two subscribers and attach them to our listener. But now we +add an additional parameter in the `iox::popo::createEventCallback` the +dereferenced `this` pointer. It has to be dereferenced since we require a reference +as argument. +```cpp +CounterClass() + : m_subscriberLeft({"Radar", "FrontLeft", "Counter"}) + , m_subscriberRight({"Radar", "FrontRight", "Counter"}) +{ + /// Attach the static method onSampleReceivedCallback and provide "*this" as additional argument + /// to the callback to gain access to the object whenever the callback is called. + /// It is not possible to use a lambda with capturing here since they are not convertable to + /// a C function pointer. + m_listener + .attachEvent(m_subscriberLeft, + iox::popo::SubscriberEvent::DATA_RECEIVED, + iox::popo::createEventCallback(onSampleReceivedCallback, *this)) + .or_else([](auto) { + std::cerr << "unable to attach subscriberLeft" << std::endl; + std::terminate(); + }); + m_listener + .attachEvent(m_subscriberRight, + iox::popo::SubscriberEvent::DATA_RECEIVED, + iox::popo::createEventCallback(onSampleReceivedCallback, *this)) + .or_else([](auto) { + std::cerr << "unable to attach subscriberRight" << std::endl; + std::terminate(); + }); +} +``` + +The `onSampleReceivedCallback` is now a static method instead of a free function. It +has to be static since we require a C function reference as callback argument and a +static method can be converted into such a type. But in a static method we do not +have access to the members of an object, therefore we have to add an additional +argument, the pointer to the object itself, called `self`. +```cpp +static void onSampleReceivedCallback(iox::popo::Subscriber* subscriber, + CounterClass* self) +{ + subscriber->take().and_then([subscriber, self](auto& sample) { + auto instanceString = subscriber->getServiceDescription().getInstanceIDString(); + + // store the sample in the corresponding cache + if (instanceString == iox::capro::IdString_t("FrontLeft")) + { + self->m_leftCache.emplace(*sample); + } + else if (instanceString == iox::capro::IdString_t("FrontRight")) + { + self->m_rightCache.emplace(*sample); + } + + std::cout << "received: " << sample->counter << std::endl; + }); + + // if both caches are filled we can process them + if (self->m_leftCache && self->m_rightCache) + { + std::cout << "Received samples from FrontLeft and FrontRight. Sum of " << self->m_leftCache->counter + << " + " << self->m_rightCache->counter << " = " + << self->m_leftCache->counter + self->m_rightCache->counter << std::endl; + self->m_leftCache.reset(); + self->m_rightCache.reset(); + } +} +``` + +
[Check out callbacks on GitHub :fontawesome-brands-github:](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/callbacks){ .md-button }
diff --git a/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp b/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp index 36518e87d26..e723499665d 100644 --- a/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp +++ b/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp @@ -110,11 +110,11 @@ class CounterClass } } - iox::popo::Listener m_listener; iox::popo::Subscriber m_subscriberLeft; iox::popo::Subscriber m_subscriberRight; iox::cxx::optional m_leftCache; iox::cxx::optional m_rightCache; + iox::popo::Listener m_listener; }; int main() diff --git a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp index cbd49d1c1bc..8ce8c3ec640 100644 --- a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp +++ b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp @@ -89,6 +89,16 @@ class Listener Listener& operator=(const Listener&) = delete; Listener& operator=(Listener&&) = delete; + /// @brief Attaches an event. Hereby the event is defined as a class T, the eventOrigin, an enum which further + /// defines the event inside the class and the corresponding callback which will be called when the event + /// occurs. + /// @note This method can be called from any thread concurrently without any restrictions! + /// @tparam[in] T type of the class which will signal the event + /// @param[in] eventOrigin the object which will signal the event (the origin) + /// @param[in] eventType enum required to specify the type of event inside of eventOrigin + /// @param[in] eventCallback callback which will be executed concurrently when the event occurs. has to be created + /// with iox::popo::createEventCallback + /// @return If an error occurs the enum packed inside an expected which describes the error. template attachEvent(T& eventOrigin, const EventType eventType, const EventCallback& eventCallback) noexcept; + /// @brief Attaches an event. Hereby the event is defined as a class T, the eventOrigin and + /// the corresponding callback which will be called when the event occurs. + /// @note This method can be called from any thread concurrently without any restrictions! + /// @tparam[in] T type of the class which will signal the event + /// @param[in] eventOrigin the object which will signal the event (the origin) + /// @param[in] eventCallback callback which will be executed concurrently when the event occurs. Has to be created + /// with iox::popo::createEventCallback + /// @return If an error occurs the enum packed inside an expected which describes the error. template cxx::expected attachEvent(T& eventOrigin, const EventCallback& eventCallback) noexcept; From 63e3bfe21013170e37d22b2bfb255b06efc711ac Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 13 Apr 2021 03:23:20 +0200 Subject: [PATCH 035/127] iox-#707 fixed mac os/clang issue with template default type value in implementation Signed-off-by: Christian Eltzschig --- .../include/iceoryx_posh/internal/popo/event_callback.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl b/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl index 8206e75742d..aedc6ff097d 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl @@ -21,7 +21,7 @@ namespace iox { namespace popo { -template +template inline EventCallback createEventCallback(void (&callback)(OriginType* const)) { return EventCallback{&callback}; From 5df32df14619a5931cc2724669cc27158ebb0593 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 13 Apr 2021 03:40:56 +0200 Subject: [PATCH 036/127] iox-#707 adjusted listener storage size in c binding for mac os Signed-off-by: Christian Eltzschig --- iceoryx_binding_c/include/iceoryx_binding_c/types.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iceoryx_binding_c/include/iceoryx_binding_c/types.h b/iceoryx_binding_c/include/iceoryx_binding_c/types.h index a5631894d88..f1b9b88bfbe 100644 --- a/iceoryx_binding_c/include/iceoryx_binding_c/types.h +++ b/iceoryx_binding_c/include/iceoryx_binding_c/types.h @@ -75,7 +75,11 @@ struct iox_listener_storage_t_ { // the value of the array size is the result of the following formula: // sizeof(WaitSet) / 8 +#if defined(__APPLE__) + uint64_t do_not_touch_me[2643]; +#else uint64_t do_not_touch_me[2567]; +#endif }; typedef struct iox_listener_storage_t_ iox_listener_storage_t; From a612f35bf16b98328b15e13c62bd42efc70e905a Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 13 Apr 2021 09:23:56 +0200 Subject: [PATCH 037/127] iox-#707 fixed undefined behavior caused by wrong function reconstruction casting Signed-off-by: Christian Eltzschig --- .../iceoryx_posh/internal/popo/event_callback.inl | 2 +- .../include/iceoryx_posh/internal/popo/listener.inl | 7 ++++--- iceoryx_posh/include/iceoryx_posh/popo/listener.hpp | 10 ---------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl b/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl index aedc6ff097d..f757e30c4d4 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/popo/event_callback.inl @@ -24,7 +24,7 @@ namespace popo template inline EventCallback createEventCallback(void (&callback)(OriginType* const)) { - return EventCallback{&callback}; + return EventCallback{&callback}; } template diff --git a/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl b/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl index 9a295788701..a1181d7a03a 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/popo/listener.inl @@ -27,18 +27,19 @@ struct TranslateAndCallTypelessCallback { static void call(void* const origin, void* const userType, Listener::GenericCallbackPtr_t underlyingCallback) { - reinterpret_cast>(underlyingCallback)( + reinterpret_cast::Ptr_t>(underlyingCallback)( static_cast(origin), static_cast(userType)); } }; template -struct TranslateAndCallTypelessCallback +struct TranslateAndCallTypelessCallback { static void call(void* const origin, void* const userType, Listener::GenericCallbackPtr_t underlyingCallback) { IOX_DISCARD_RESULT(userType); - reinterpret_cast>(underlyingCallback)(static_cast(origin)); + reinterpret_cast::Ptr_t>(underlyingCallback)( + static_cast(origin)); } }; } // namespace internal diff --git a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp index 8ce8c3ec640..f49251dfaf5 100644 --- a/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp +++ b/iceoryx_posh/include/iceoryx_posh/popo/listener.hpp @@ -71,16 +71,6 @@ class Listener using TranslationCallbackRef_t = void (&)(void* const, void* const, GenericCallbackPtr_t const); using TranslationCallbackPtr_t = void (*)(void* const, void* const, GenericCallbackPtr_t const); - template - using CallbackRef_t = void (&)(T* const); - template - using CallbackWithUserTypeRef_t = void (&)(T* const, UserType* const); - - template - using CallbackPtr_t = void (*)(T* const); - template - using CallbackWithUserTypePtr_t = void (*)(T* const, UserType* const); - Listener() noexcept; Listener(const Listener&) = delete; Listener(Listener&&) = delete; From b616107a2d19876b4e3b6e79a7c60191e95a15c9 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 13 Apr 2021 16:24:04 +0200 Subject: [PATCH 038/127] iox-#707 fixed spelling mistakes and renamed example methods for better understanding Signed-off-by: Christian Eltzschig --- .../include/iceoryx_binding_c/types.h | 2 +- iceoryx_examples/callbacks/README.md | 19 ++++++++++++------- ...ice_callbacks_listener_as_class_member.cpp | 10 +++++----- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/iceoryx_binding_c/include/iceoryx_binding_c/types.h b/iceoryx_binding_c/include/iceoryx_binding_c/types.h index f1b9b88bfbe..4657b177ab8 100644 --- a/iceoryx_binding_c/include/iceoryx_binding_c/types.h +++ b/iceoryx_binding_c/include/iceoryx_binding_c/types.h @@ -74,7 +74,7 @@ typedef struct iox_pub_storage_t_ iox_pub_storage_t; struct iox_listener_storage_t_ { // the value of the array size is the result of the following formula: - // sizeof(WaitSet) / 8 + // sizeof(Listener) / 8 #if defined(__APPLE__) uint64_t do_not_touch_me[2643]; #else diff --git a/iceoryx_examples/callbacks/README.md b/iceoryx_examples/callbacks/README.md index 71cd6b22bd4..a6262adf0aa 100644 --- a/iceoryx_examples/callbacks/README.md +++ b/iceoryx_examples/callbacks/README.md @@ -194,10 +194,15 @@ Afterward, we reset both caches to start fresh again. } ``` -### ice_callbacks_listener_as_class_member.cpp +### Additional user defined argument for callbacks (ice_callbacks_listener_as_class_member.cpp) + +Here we demonstrate how you can virtually everything as an additional argument to the callbacks. +You just have to provide a reference to a value as additional argument in the `attachEvent` method +which is then provided as argument in your callback. One of the use cases is to get access +to members and methods of an object inside a static method which we demonstrate here. This example is identical to the [ice_callbacks_subscriber.cpp](#ice_callbacks_subscriber.cpp) -one except that we left out the cyclic heartbeat trigger. The key difference is that +one, except that we left out the cyclic heartbeat trigger. The key difference is that the listener is now a class member and in every callback we would like to change some member variables. To do this we require an additional pointer to the object since the listener requires c function references which do not allow the usage @@ -205,7 +210,7 @@ of lambdas with capturing. Here we can use the userType feature which allows us to provide the this pointer as additional argument to the callback. The main function is now pretty short, we instantiate our object of type `CounterClass` -and call `waitForControlC` which uses the `shutdownSemaphore` like in the +and call `waitForShutdown` which uses the `shutdownSemaphore` like in the previous example to wait for the control c event from the user. ```cpp auto signalIntGuard = iox::posix::registerSignalHandler(iox::posix::Signal::INT, sigHandler); @@ -215,7 +220,7 @@ iox::runtime::PoshRuntime::initRuntime(APP_NAME); CounterClass counterClass; -counterClass.waitForControlC(); +counterClass.waitForShutdown(); ``` Our `CounterClass` has the following members: @@ -232,7 +237,7 @@ class CounterClass { And their purposes are the same as in the previous example. In the constructor we initialize the two subscribers and attach them to our listener. But now we -add an additional parameter in the `iox::popo::createEventCallback` the +add an additional parameter in the `iox::popo::createEventCallback`, the dereferenced `this` pointer. It has to be dereferenced since we require a reference as argument. ```cpp @@ -250,7 +255,7 @@ CounterClass() iox::popo::createEventCallback(onSampleReceivedCallback, *this)) .or_else([](auto) { std::cerr << "unable to attach subscriberLeft" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); m_listener .attachEvent(m_subscriberRight, @@ -258,7 +263,7 @@ CounterClass() iox::popo::createEventCallback(onSampleReceivedCallback, *this)) .or_else([](auto) { std::cerr << "unable to attach subscriberRight" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); } ``` diff --git a/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp b/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp index e723499665d..00b38070e77 100644 --- a/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp +++ b/iceoryx_examples/callbacks/ice_callbacks_listener_as_class_member.cpp @@ -37,7 +37,7 @@ static void sigHandler(int f_sig [[gnu::unused]]) { shutdownSemaphore.post().or_else([](auto) { std::cerr << "unable to call post on shutdownSemaphore - semaphore corrupt?" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); keepRunning = false; } @@ -59,7 +59,7 @@ class CounterClass iox::popo::createEventCallback(onSampleReceivedCallback, *this)) .or_else([](auto) { std::cerr << "unable to attach subscriberLeft" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); m_listener .attachEvent(m_subscriberRight, @@ -67,11 +67,11 @@ class CounterClass iox::popo::createEventCallback(onSampleReceivedCallback, *this)) .or_else([](auto) { std::cerr << "unable to attach subscriberRight" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); } - void waitForControlC() noexcept + void waitForShutdown() noexcept { shutdownSemaphore.wait().or_else( [](auto) { std::cerr << "unable to call wait on shutdownSemaphore - semaphore corrupt?" << std::endl; }); @@ -126,7 +126,7 @@ int main() CounterClass counterClass; - counterClass.waitForControlC(); + counterClass.waitForShutdown(); return (EXIT_SUCCESS); } From 82670327eeeceb2ecafa001c58d1861287ccc8b8 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Tue, 13 Apr 2021 16:51:08 +0200 Subject: [PATCH 039/127] iox-#707 spelling mistake and using std::exit instead of std::terminate Signed-off-by: Christian Eltzschig --- iceoryx_examples/callbacks/README.md | 8 ++++---- iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/iceoryx_examples/callbacks/README.md b/iceoryx_examples/callbacks/README.md index a6262adf0aa..33ed13c2341 100644 --- a/iceoryx_examples/callbacks/README.md +++ b/iceoryx_examples/callbacks/README.md @@ -89,7 +89,7 @@ callback (`heartbeatCallback`). listener.attachEvent(heartbeat, iox::popo::createEventCallback(heartbeatCallback)) .or_else([](auto) { std::cerr << "unable to attach heartbeat event" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); listener.attachEvent(subscriberLeft, @@ -97,7 +97,7 @@ listener.attachEvent(subscriberLeft, iox::popo::createEventCallback(onSampleReceivedCallback)) .or_else([](auto) { std::cerr << "unable to attach subscriberLeft" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // It is possible to attach any c function here with a signature of void(iox::popo::Subscriber *). @@ -110,7 +110,7 @@ listener.attachEvent(subscriberRight, iox::popo::createEventCallback(onSampleReceivedCallback)) .or_else([](auto) { std::cerr << "unable to attach subscriberRight" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); ``` @@ -196,7 +196,7 @@ Afterward, we reset both caches to start fresh again. ### Additional user defined argument for callbacks (ice_callbacks_listener_as_class_member.cpp) -Here we demonstrate how you can virtually everything as an additional argument to the callbacks. +Here we demonstrate how you can provide virtually everything as an additional argument to the callbacks. You just have to provide a reference to a value as additional argument in the `attachEvent` method which is then provided as argument in your callback. One of the use cases is to get access to members and methods of an object inside a static method which we demonstrate here. diff --git a/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp b/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp index 33b9f57ac51..7ec02668bd3 100644 --- a/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp +++ b/iceoryx_examples/callbacks/ice_callbacks_subscriber.cpp @@ -40,7 +40,7 @@ static void sigHandler(int f_sig [[gnu::unused]]) { shutdownSemaphore.post().or_else([](auto) { std::cerr << "unable to call post on shutdownSemaphore - semaphore corrupt?" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); keepRunning = false; } @@ -105,7 +105,7 @@ int main() // attach everything to the listener, from here on the callbacks are called when the corresponding event is occuring listener.attachEvent(heartbeat, iox::popo::createEventCallback(heartbeatCallback)).or_else([](auto) { std::cerr << "unable to attach heartbeat event" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // It is possible to attach any c function here with a signature of void(iox::popo::Subscriber *). @@ -119,7 +119,7 @@ int main() iox::popo::createEventCallback(onSampleReceivedCallback)) .or_else([](auto) { std::cerr << "unable to attach subscriberLeft" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); listener .attachEvent(subscriberRight, @@ -127,7 +127,7 @@ int main() iox::popo::createEventCallback(onSampleReceivedCallback)) .or_else([](auto) { std::cerr << "unable to attach subscriberRight" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // wait until someone presses CTRL+c From 8232bbd70313fe560780643563d6badd0fb7e8e1 Mon Sep 17 00:00:00 2001 From: "Hintz Martin (XC-AD/ESB5)" Date: Wed, 14 Apr 2021 08:47:18 +0200 Subject: [PATCH 040/127] iox-#558 Use platform include Signed-off-by: Hintz Martin (XC-AD/ESB5) --- iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp | 1 + iceoryx_posh/test/fuzztests/fuzzing.cpp | 3 ++- iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp | 2 +- iceoryx_posh/test/fuzztests/roudi_fuzz.cpp | 1 + iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp | 2 ++ 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp index da8b30fb858..5d43a0fef35 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp @@ -18,6 +18,7 @@ #include "fuzz_helper.hpp" #include "iceoryx_posh/internal/log/posh_logging.hpp" #include "iceoryx_utils/platform/getopt.hpp" + #include #include #include diff --git a/iceoryx_posh/test/fuzztests/fuzzing.cpp b/iceoryx_posh/test/fuzztests/fuzzing.cpp index 99ebc63285f..6e3f9111795 100644 --- a/iceoryx_posh/test/fuzztests/fuzzing.cpp +++ b/iceoryx_posh/test/fuzztests/fuzzing.cpp @@ -16,8 +16,9 @@ #include "fuzzing.hpp" #include "cpptoml.h" +#include "iceoryx_utils/platform/socket.hpp" + #include -#include #include std::string const UDS_NAME = "/tmp/"; diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index e6e3327bae7..905c35a0664 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -14,7 +14,6 @@ // // SPDX-License-Identifier: Apache-2.0 - #include "fuzztests_roudi_wrapper.hpp" #include "cmdlineparserfuzzing.hpp" #include "fuzz_helper.hpp" @@ -22,6 +21,7 @@ #include "iceoryx_posh/internal/log/posh_logging.hpp" #include "iceoryx_posh/internal/roudi/roudi.hpp" #include "iceoryx_posh/roudi/iceoryx_roudi_components.hpp" + #include #include #include diff --git a/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp b/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp index f6be661bce8..47f91b2f72d 100644 --- a/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp +++ b/iceoryx_posh/test/fuzztests/roudi_fuzz.cpp @@ -16,6 +16,7 @@ #include "roudi_fuzz.hpp" #include "string_to_ipc_message.hpp" + RouDiFuzz::RouDiFuzz(iox::roudi::RouDiMemoryInterface& roudiMemoryInterface, iox::roudi::PortManager& portManager, iox::roudi::RouDi::RoudiStartupParameters aStartupParameter) diff --git a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp index 94a9d8f102b..0fbde04b62e 100644 --- a/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp +++ b/iceoryx_posh/test/fuzztests/string_to_ipc_message.hpp @@ -17,6 +17,8 @@ #ifndef STRINGTOIPCMESSAGE_HPP #define STRINGTOIPCMESSAGE_HPP +#include "iceoryx_posh/internal/runtime/ipc_interface_base.hpp" + /// @brief The StringToIPCMessage is a class which inherits from iox::runtime::IpcInterfaceBase to make the protected /// method iox::runtime::IpcInterfaceBase::setMessageFromString public and accessible for the fuzz test. From 89c31e65ce20fc4feae6b37eda929f9b4d179806 Mon Sep 17 00:00:00 2001 From: "Hintz Martin (XC-AD/ESB5)" Date: Wed, 14 Apr 2021 09:25:38 +0200 Subject: [PATCH 041/127] iox-#558 Replace alternative tokens Signed-off-by: Hintz Martin (XC-AD/ESB5) --- iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index 905c35a0664..f495a5d53d4 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -57,7 +57,7 @@ int main(int argc, char* argv[]) return -1; } - if (cmd.getInputMode() == InputMode::CL and !cmd.getCmdLineFlag()) + if ((cmd.getInputMode() == InputMode::CL) && (!cmd.getCmdLineFlag())) { std::cout << "Please use -i [INPUT_MESSAGE] or -c [PATH_To_File] to enter a String which you want to send to " "the interface. It is also possible to use -m stdin instead." @@ -82,7 +82,7 @@ int main(int argc, char* argv[]) } } - if (cmd.getFuzzingAPI() == FuzzingApi::UDS or cmd.getFuzzingAPI() == FuzzingApi::COM) // Start RouDi + if ((cmd.getFuzzingAPI() == FuzzingApi::UDS) || (cmd.getFuzzingAPI() == FuzzingApi::COM)) // Start RouDi { aRouDi = aFuzzHelper.startRouDiThread(); unsigned char timeout = 0; @@ -98,7 +98,7 @@ int main(int argc, char* argv[]) } } - if (cmd.getInputMode() == InputMode::CL or cmd.getInputMode() == InputMode::STDIN) + if ((cmd.getInputMode() == InputMode::CL) || (cmd.getInputMode() == InputMode::STDIN)) { Fuzzing aFuzzer; for (std::string aMessage : allMessages) From f3884c9dbf6e5ae458ad9e785b17f211e98ad7e7 Mon Sep 17 00:00:00 2001 From: "Hintz Martin (XC-AD/ESB5)" Date: Wed, 14 Apr 2021 12:35:56 +0200 Subject: [PATCH 042/127] iox-#558 Move breaks Signed-off-by: Hintz Martin (XC-AD/ESB5) --- .../fuzztests/fuzztests_roudi_wrapper.cpp | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index f495a5d53d4..3d8c559ca54 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -34,13 +34,13 @@ int main(int argc, char* argv[]) if (cmd.getHelpFlag()) { - return 1; + return EXIT_SUCCESS; } if (cmd.getErrorFlag()) { std::cout << "No or wrong command lines were specified. Please use --help!" << std::endl; - return -1; + return EXIT_FAILURE; } if (allMessages.empty()) @@ -48,13 +48,13 @@ int main(int argc, char* argv[]) std::cout << "Please use -m [cl, stdin] to enter the input you want to send to the executable. If you use -m " "cl, then you also need use -i [INPUT_MESSAGE] or -c [PATH_To_File] to specify the message." << std::endl; - return -1; + return EXIT_FAILURE; } if (cmd.getInputMode() == InputMode::NONE) { std::cout << "Please use -m to specify the input. Please use --help to get more information." << std::endl; - return -1; + return EXIT_FAILURE; } if ((cmd.getInputMode() == InputMode::CL) && (!cmd.getCmdLineFlag())) @@ -62,7 +62,7 @@ int main(int argc, char* argv[]) std::cout << "Please use -i [INPUT_MESSAGE] or -c [PATH_To_File] to enter a String which you want to send to " "the interface. It is also possible to use -m stdin instead." << std::endl; - return -1; + return EXIT_FAILURE; } FuzzHelper aFuzzHelper; std::shared_ptr aRouDi; @@ -74,7 +74,7 @@ int main(int argc, char* argv[]) std::cout << "Please use -t [PATH_To_File] to specify a file where the messages are written to which are " "sent to the TOML configuration parser." << std::endl; - return -1; + return EXIT_FAILURE; } else { @@ -91,7 +91,7 @@ int main(int argc, char* argv[]) if (timeout >= TIMEOUT) { std::cout << "RouDi could not be started, program terminates!" << std::endl; - return -1; + return EXIT_FAILURE; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 1/10 of a second timeout += 1; @@ -109,29 +109,28 @@ int main(int argc, char* argv[]) { iox::LogDebug() << "Messages sent to RouDi: " << aMessage; aFuzzer.fuzzingRouDiCom(aRouDi, aMessage); + break; } - break; case FuzzingApi::UDS: { aFuzzer.fuzzingRouDiUDS(aMessage); iox::LogDebug() << "Messages sent to RouDi: " << aMessage; + break; } - break; case FuzzingApi::TOML: { aFuzzer.fuzzingTOMLParser(aMessage, cmd.getTomlFile()); iox::LogDebug() << "Messages sent to TOML Parser: " << aMessage; + break; } - break; default: { std::cout << "Error: Unkown Fuzzing API parameter" << std::endl; - return -1; + return EXIT_FAILURE; } - break; }; } } @@ -141,6 +140,6 @@ int main(int argc, char* argv[]) std::cout << "Error: Only stdin and command line are allowed to enter an input. Please use --help to get more " "information." << std::endl; - return -1; + return EXIT_FAILURE; } } From 6d6645029b7bbf719a4420d5968f81342c1f4c5c Mon Sep 17 00:00:00 2001 From: "Hintz Martin (XC-AD/ESB5)" Date: Wed, 14 Apr 2021 12:43:53 +0200 Subject: [PATCH 043/127] iox-#558 Refactor retry loop Signed-off-by: Hintz Martin (XC-AD/ESB5) --- .../test/fuzztests/fuzztests_roudi_wrapper.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp index 3d8c559ca54..43019a17b3b 100644 --- a/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp +++ b/iceoryx_posh/test/fuzztests/fuzztests_roudi_wrapper.cpp @@ -28,7 +28,8 @@ int main(int argc, char* argv[]) { - constexpr unsigned char TIMEOUT = 50; // 5s for a Timeout + constexpr uint8_t MAX_RETRIES = 50; + constexpr uint64_t WAIT_RETRY_IN_MS = 100; CmdLineParserFuzzing cmd; std::vector allMessages = cmd.parseCmd(argc, argv); @@ -64,6 +65,7 @@ int main(int argc, char* argv[]) << std::endl; return EXIT_FAILURE; } + FuzzHelper aFuzzHelper; std::shared_ptr aRouDi; @@ -85,16 +87,17 @@ int main(int argc, char* argv[]) if ((cmd.getFuzzingAPI() == FuzzingApi::UDS) || (cmd.getFuzzingAPI() == FuzzingApi::COM)) // Start RouDi { aRouDi = aFuzzHelper.startRouDiThread(); - unsigned char timeout = 0; + + uint8_t retryCounter{0}; while (!aFuzzHelper.checkIsRouDiRunning()) { - if (timeout >= TIMEOUT) + if (retryCounter >= MAX_RETRIES) { std::cout << "RouDi could not be started, program terminates!" << std::endl; return EXIT_FAILURE; } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 1/10 of a second - timeout += 1; + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_RETRY_IN_MS)); + retryCounter++; } } @@ -134,7 +137,6 @@ int main(int argc, char* argv[]) }; } } - else { std::cout << "Error: Only stdin and command line are allowed to enter an input. Please use --help to get more " From b7239578c96c2da60de97b6b4651e27ef8929a10 Mon Sep 17 00:00:00 2001 From: "Hintz Martin (XC-AD/ESB5)" Date: Wed, 14 Apr 2021 12:52:03 +0200 Subject: [PATCH 044/127] iox-#558 Fix breaks Signed-off-by: Hintz Martin (XC-AD/ESB5) --- .../test/fuzztests/cmdlineparserfuzzing.cpp | 20 +++++++------------ .../test/fuzztests/cmdlineparserfuzzing.hpp | 8 ++++---- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp index 5d43a0fef35..f9c1cf4f0eb 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.cpp @@ -123,9 +123,8 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) std::cout << "-l, --log-level" << std::endl; std::cout << "\t {off, fatal, debug} : Set the log level. Off is default;" << std::endl; m_helpFlag = true; + break; } - break; - case 'f': { if (strcmp(optarg, "uds") == 0) @@ -146,9 +145,8 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) m_errorFlag = true; return m_allMessages; } + break; } - break; - case 'm': { if (strcmp(optarg, "stdin") == 0) @@ -167,15 +165,14 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) m_errorFlag = true; return m_allMessages; } + break; } - break; case 'i': { m_cmdLineFlag = true; m_allMessages.emplace_back(optarg); + break; } - break; - case 'c': { m_cmdLineFlag = true; @@ -187,7 +184,6 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) (std::istreambuf_iterator())); m_allMessages.emplace_back(tempFileContent); } - else { std::cout @@ -195,9 +191,8 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) m_errorFlag = true; return m_allMessages; } + break; } - break; - case 'l': { if (strcmp(optarg, "off") == 0) @@ -217,21 +212,20 @@ std::vector CmdLineParserFuzzing::parseCmd(int argc, char* argv[]) std::cout << "Options for Logging are 'off', 'fatal' and 'debug'!" << std::endl; } iox::log::LogManager::GetLogManager().SetDefaultLogLevel(m_logLevel); + break; } - break; case 't': { m_tomlFileFlag = true; m_tomlFile = optarg; + break; } - break; default: { std::cout << "Unknown command.\n" << std::endl; m_errorFlag = true; return m_allMessages; } - break; }; } return m_allMessages; diff --git a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp index 7238685c611..5fa8f1e906a 100644 --- a/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp +++ b/iceoryx_posh/test/fuzztests/cmdlineparserfuzzing.hpp @@ -78,13 +78,13 @@ class CmdLineParserFuzzing std::string getTomlFile(); private: - std::vector m_allMessages; - bool m_helpFlag; - InputMode m_inputMode; bool m_errorFlag; bool m_cmdLineFlag; - FuzzingApi m_fuzzingAPI; + bool m_helpFlag; bool m_tomlFileFlag; + InputMode m_inputMode; + FuzzingApi m_fuzzingAPI; std::string m_tomlFile; + std::vector m_allMessages; }; #endif // CMDLINEPARSERFUZZING_HPP From af3913217683eff3e479d03b8b652877eec792a0 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Mon, 29 Mar 2021 20:57:42 +0200 Subject: [PATCH 045/127] iox-#482 Add icecubetray example and restrict access rights of UNIX domain sockets Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/CMakeLists.txt | 68 ++++++++++++++++ iceoryx_examples/icecubetray/README.md | 68 ++++++++++++++++ .../icecubetray/iox_display_app.cpp | 79 +++++++++++++++++++ .../icecubetray/iox_radar_app.cpp | 74 +++++++++++++++++ .../roudi_main_static_segements.cpp | 52 ++++++++++++ .../icecubetray/run_icecubetray.sh | 29 +++++++ iceoryx_examples/icecubetray/topic_data.hpp | 37 +++++++++ iceoryx_meta/CMakeLists.txt | 1 + .../posix_wrapper/shared_memory_object.hpp | 2 +- .../posix_wrapper/unix_domain_socket.cpp | 14 +++- tools/iceoryx_build_test.sh | 2 +- 11 files changed, 420 insertions(+), 6 deletions(-) create mode 100644 iceoryx_examples/icecubetray/CMakeLists.txt create mode 100644 iceoryx_examples/icecubetray/README.md create mode 100644 iceoryx_examples/icecubetray/iox_display_app.cpp create mode 100644 iceoryx_examples/icecubetray/iox_radar_app.cpp create mode 100644 iceoryx_examples/icecubetray/roudi_main_static_segements.cpp create mode 100644 iceoryx_examples/icecubetray/run_icecubetray.sh create mode 100644 iceoryx_examples/icecubetray/topic_data.hpp diff --git a/iceoryx_examples/icecubetray/CMakeLists.txt b/iceoryx_examples/icecubetray/CMakeLists.txt new file mode 100644 index 00000000000..923f9d96b6d --- /dev/null +++ b/iceoryx_examples/icecubetray/CMakeLists.txt @@ -0,0 +1,68 @@ +# Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +# Build icecubetray example +cmake_minimum_required(VERSION 3.5) +project(example_icecubetray) + +option(TOML_CONFIG "TOML support for RouDi with dynamic configuration" OFF) + +find_package(iceoryx_posh CONFIG REQUIRED) +find_package(iceoryx_binding_c CONFIG REQUIRED) +find_package(iceoryx_utils CONFIG REQUIRED) + +get_target_property(ICEORYX_CXX_STANDARD iceoryx_posh::iceoryx_posh CXX_STANDARD) +include(IceoryxPlatform) + +add_executable(iox-cpp-radar iox_radar_app.cpp) + +target_link_libraries(iox-cpp-radar + iceoryx_posh::iceoryx_posh +) +target_compile_options(iox-cpp-radar PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) + + +add_executable(iox-cpp-display iox_display_app.cpp) + + +target_link_libraries(iox-cpp-display + iceoryx_posh::iceoryx_posh +) +target_compile_options(iox-cpp-display PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) + +add_executable(iox-cpp-roudi-static-segments + roudi_main_static_segements.cpp +) + +target_link_libraries(iox-cpp-roudi-static-segments + PRIVATE + iceoryx_utils::iceoryx_utils + iceoryx_posh::iceoryx_posh_roudi +) + +target_compile_options(iceperf-roudi PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) + +set_target_properties(iox-cpp-radar iox-cpp-display iox-cpp-roudi-static-segments PROPERTIES + CXX_STANDARD_REQUIRED ON + CXX_STANDARD ${ICEORYX_CXX_STANDARD} + POSITION_INDEPENDENT_CODE ON +) + + +install( + TARGETS iox-cpp-radar iox-cpp-display iox-cpp-roudi-static-segments + RUNTIME DESTINATION bin +) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/icecubetray/README.md new file mode 100644 index 00000000000..b95bdfa1ba3 --- /dev/null +++ b/iceoryx_examples/icecubetray/README.md @@ -0,0 +1,68 @@ +# icecubetray + +## Introduction + +This example demonstrates how access rights can be applied to shared memory segments. +It provides a custom RouDi, a radar and a display application. + +## Expected output + + + +## Code walkthrough + + + +| Users | privileged group | unprivileged group | infotainment group | iceoryx group | +|--------------|:----------------:|:------------------:|:------------------:|:------------------:| +| perception | X | | | X | +| infotainment | | X | X | X | +| roudi | | | | X | + +### RouDi with static shared memory segments + +The segments are configured as depicted below: + +``` + +-----------------------+ ++--------------------+ | | +---------------------+ +| Radar App | | Privileged Shared | | Some Other App | +| @perception | | Memory Segment | | @perception | +| | publish | | subscribe | | +| | -------> | r: unprivileged | ------> | | +| | | w: privileged | | | +| | | | | | +| | | | <------ | | +| | | | publish | | ++--------------------+ | | +---------------------+ + +-----------------------+ + | + | subscribe + +---------------------------+ + | + | + | + \ / + +-----------------------+ + | | +---------------------+ + | Infotainment Shared | | Display App | + | Memory Segment | publish | @infotainment | + | | <------ | | + | r: infotainment | | | + | w: infotainment | | | + | | | | + | | | | + | | | | + | | +---------------------+ + +-----------------------+ +``` + +It's advised to create per writer group only one shared memory segement (e.g. not two segements with `w: infotainment`). +In this case it wouldn't be possible to control which segment will be used. + +!!! note + Note the shared memory managment segment is always available for everyone to **read** and **write** + +### Radar App + +### Display App diff --git a/iceoryx_examples/icecubetray/iox_display_app.cpp b/iceoryx_examples/icecubetray/iox_display_app.cpp new file mode 100644 index 00000000000..1cefcf02129 --- /dev/null +++ b/iceoryx_examples/icecubetray/iox_display_app.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "topic_data.hpp" + +#include "iceoryx_posh/popo/publisher.hpp" +#include "iceoryx_posh/popo/subscriber.hpp" +#include "iceoryx_posh/runtime/posh_runtime.hpp" +#include "iceoryx_utils/posix_wrapper/signal_handler.hpp" + +#include + +bool killswitch = false; +constexpr char APP_NAME[] = "iox-cpp-display"; + +static void sigHandler(int f_sig [[gnu::unused]]) +{ + // caught SIGINT or SIGTERM, now exit gracefully + killswitch = true; +} + +int main() +{ + // register sigHandler + auto signalIntGuard = iox::posix::registerSignalHandler(iox::posix::Signal::INT, sigHandler); + auto signalTermGuard = iox::posix::registerSignalHandler(iox::posix::Signal::TERM, sigHandler); + + // initialize runtime + iox::runtime::PoshRuntime::initRuntime(APP_NAME); + + // initialized subscriber + iox::popo::Subscriber subscriber({"Radar", "FrontLeft", "Object"}); + iox::popo::Publisher publisher({"Radar", "HMI-Display", "Object"}); + + // run until interrupted by Ctrl-C + while (!killswitch) + { + auto takeResult = subscriber.take(); + + if (!takeResult.has_error()) + { + publisher.loan().and_then([&](auto& sample) { + sample->x = 2 * takeResult.value()->x; + sample->y = 2 * takeResult.value()->y; + sample->z = 2 * takeResult.value()->z; + std::cout << APP_NAME << " sending value: " << takeResult.value()->x << std::endl; + sample.publish(); + }); + } + else + { + if (takeResult.get_error() == iox::popo::ChunkReceiveResult::NO_CHUNK_AVAILABLE) + { + std::cout << "No chunk available." << std::endl; + } + else + { + std::cout << "Error receiving chunk." << std::endl; + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + return (EXIT_SUCCESS); +} diff --git a/iceoryx_examples/icecubetray/iox_radar_app.cpp b/iceoryx_examples/icecubetray/iox_radar_app.cpp new file mode 100644 index 00000000000..e671a68079f --- /dev/null +++ b/iceoryx_examples/icecubetray/iox_radar_app.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "topic_data.hpp" + +#include "iceoryx_posh/popo/publisher.hpp" +#include "iceoryx_posh/runtime/posh_runtime.hpp" +#include "iceoryx_utils/posix_wrapper/signal_handler.hpp" + +#include + +bool killswitch = false; +constexpr char APP_NAME[] = "iox-cpp-radar"; + +static void sigHandler(int f_sig [[gnu::unused]]) +{ + // caught SIGINT or SIGTERM, now exit gracefully + killswitch = true; +} + +int main() +{ + // Register sigHandler + auto signalIntGuard = iox::posix::registerSignalHandler(iox::posix::Signal::INT, sigHandler); + auto signalTermGuard = iox::posix::registerSignalHandler(iox::posix::Signal::TERM, sigHandler); + + // Let the user define the runtime name via the first command line parameter + iox::runtime::PoshRuntime::initRuntime(APP_NAME); + + iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); + + double ct = 0.0; + while (!killswitch) + { + ++ct; + + // Retrieve a sample from shared memory + auto loanResult = publisher.loan(); + if (!loanResult.has_error()) + { + auto& sample = loanResult.value(); + // Sample can be held until ready to publish + sample->x = ct; + sample->y = ct; + sample->z = ct; + sample.publish(); + } + else + { + auto error = loanResult.get_error(); + // Do something with error + std::cerr << "Unable to loan sample, error code: " << static_cast(error) << std::endl; + } + + std::cout << APP_NAME << " sent value: " << ct << std::endl; + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + return 0; +} diff --git a/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp b/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp new file mode 100644 index 00000000000..aef0ead8b8c --- /dev/null +++ b/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp @@ -0,0 +1,52 @@ +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "iceoryx_posh/iceoryx_posh_config.hpp" +#include "iceoryx_posh/iceoryx_posh_types.hpp" +#include "iceoryx_posh/internal/log/posh_logging.hpp" +#include "iceoryx_posh/roudi/iceoryx_roudi_app.hpp" +#include "iceoryx_posh/roudi/roudi_cmd_line_parser.hpp" + +int main(int argc, char* argv[]) +{ + using iox::roudi::IceOryxRouDiApp; + static constexpr uint32_t ONE_KILOBYTE = 1024U; + static constexpr uint32_t ONE_MEGABYTE = 1024U * 1024; + + iox::config::CmdLineParser cmdLineParser; + auto cmdLineArgs = cmdLineParser.parse(argc, argv); + if (cmdLineArgs.has_error() && (cmdLineArgs.get_error() != iox::config::CmdLineParserResult::INFO_OUTPUT_ONLY)) + { + iox::LogFatal() << "Unable to parse command line arguments!"; + return EXIT_FAILURE; + } + + iox::RouDiConfig_t roudiConfig; + + // Create Mempool Config + iox::mepoo::MePooConfig mepooConfig; + + // We only send very small data, just one mempool per segment + mepooConfig.addMemPool({128, 1000}); + + /// Create an Entry for a new Shared Memory Segment from the MempoolConfig and add it to the RouDiConfig + roudiConfig.m_sharedMemorySegments.push_back({"unprivileged", "privileged", mepooConfig}); + roudiConfig.m_sharedMemorySegments.push_back({"infotainment", "infotainment", mepooConfig}); + + IceOryxRouDiApp roudi(cmdLineArgs.value(), roudiConfig); + + return roudi.run(); +} diff --git a/iceoryx_examples/icecubetray/run_icecubetray.sh b/iceoryx_examples/icecubetray/run_icecubetray.sh new file mode 100644 index 00000000000..917cf1c85f7 --- /dev/null +++ b/iceoryx_examples/icecubetray/run_icecubetray.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +WORKSPACE=$(git rev-parse --show-toplevel) + +# Create groups +sudo groupadd -f privileged +sudo groupadd -f unprivileged +sudo groupadd -f iceoryx + +# Create users w/o homedir +sudo useradd -M perception +sudo useradd -M infotainment +sudo useradd -M roudi + +# Assign users to group and disable login +sudo usermod -L perception -a -G privileged -G iceoryx +sudo usermod -L infotainment -a -G unprivileged -G iceoryx +sudo usermod -L roudi -a -G unprivileged -G iceoryx + +# If you're using e.g Yocto or QNX refer to the manual on how to set up groups and users + +# Start custom RouDi in 'iceoryx' group +sudo -u roudi -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments + +# Start perception app as 'perception' user +sudo -u perception -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-radar + +# Start algorithm app as 'infotainment' user +sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-algorithm diff --git a/iceoryx_examples/icecubetray/topic_data.hpp b/iceoryx_examples/icecubetray/topic_data.hpp new file mode 100644 index 00000000000..6906492c310 --- /dev/null +++ b/iceoryx_examples/icecubetray/topic_data.hpp @@ -0,0 +1,37 @@ +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 +#ifndef IOX_EXAMPLES_ICECUBETRAY_TOPIC_DATA_HPP +#define IOX_EXAMPLES_ICECUBETRAY_TOPIC_DATA_HPP + +#include + +struct RadarObject +{ + RadarObject() noexcept + { + } + RadarObject(double x, double y, double z) noexcept + : x(x) + , y(y) + , z(z) + { + } + double x = 0.0; + double y = 0.0; + double z = 0.0; +}; + +#endif // IOX_EXAMPLES_ICECUBETRAY_TOPIC_DATA_HPP diff --git a/iceoryx_meta/CMakeLists.txt b/iceoryx_meta/CMakeLists.txt index d534fe7890d..398552de6ea 100644 --- a/iceoryx_meta/CMakeLists.txt +++ b/iceoryx_meta/CMakeLists.txt @@ -67,6 +67,7 @@ if(EXAMPLES) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../iceoryx_examples/waitset ${CMAKE_BINARY_DIR}/iceoryx_examples/waitset) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../iceoryx_examples/callbacks ${CMAKE_BINARY_DIR}/iceoryx_examples/callbacks) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../iceoryx_examples/singleprocess ${CMAKE_BINARY_DIR}/iceoryx_examples/singleprocess) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../iceoryx_examples/icecubetray ${CMAKE_BINARY_DIR}/iceoryx_examples/icecubetray) endif() include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/package/package.cmake) diff --git a/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object.hpp b/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object.hpp index 28bb3fdf4af..7366c1a34fe 100644 --- a/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object.hpp +++ b/iceoryx_utils/include/iceoryx_utils/internal/posix_wrapper/shared_memory_object.hpp @@ -66,7 +66,7 @@ class SharedMemoryObject : public DesignPattern::Creation UnixDomainSocket::send(const std::string& msg) co return timedSend(msg, units::Duration::fromSeconds(0ULL)); } -cxx::expected UnixDomainSocket::timedSend(const std::string& msg, - const units::Duration& timeout) const noexcept +cxx::expected UnixDomainSocket::timedSend(const std::string& msg, const units::Duration& timeout) const + noexcept { if (msg.size() >= m_maxMessageSize) // message sizes with null termination must be smaller than m_maxMessageSize { @@ -276,8 +276,8 @@ cxx::expected UnixDomainSocket::receive() const no } -cxx::expected -UnixDomainSocket::timedReceive(const units::Duration& timeout) const noexcept +cxx::expected UnixDomainSocket::timedReceive(const units::Duration& timeout) const + noexcept { if (IpcChannelSide::CLIENT == m_channelSide) { @@ -352,6 +352,12 @@ cxx::expected UnixDomainSocket::initalizeSocket(const IpcChanne return cxx::error(IpcChannelError::INVALID_ARGUMENTS); } + // the mask will be applied to the permissions, we only allow users and group members to have read and write access + // the system call always succeeds, no need to check for errors + mode_t umaskSaved = umask(S_IXUSR | S_IXGRP | S_IRWXO); + // Reset to old umask when going out of scope + cxx::GenericRAII umaskGuard([&] { umask(umaskSaved); }); + auto socketCall = cxx::makeSmartC(socket, cxx::ReturnMode::PRE_DEFINED_ERROR_CODE, {ERROR_CODE}, {}, AF_LOCAL, SOCK_DGRAM, 0); diff --git a/tools/iceoryx_build_test.sh b/tools/iceoryx_build_test.sh index bbfd27592ea..15c05a049cb 100755 --- a/tools/iceoryx_build_test.sh +++ b/tools/iceoryx_build_test.sh @@ -49,7 +49,7 @@ EXAMPLE_FLAG="OFF" BUILD_ALL_FLAG="OFF" BUILD_SHARED="OFF" TOML_FLAG="ON" -EXAMPLES="callbacks callbacks_in_c icedelivery singleprocess waitset icehello iceoptions" +EXAMPLES="callbacks callbacks_in_c icedelivery singleprocess waitset icehello iceoptions icecubetray" COMPONENTS="iceoryx_posh iceoryx_utils iceoryx_introspection iceoryx_binding_c iceoryx_component iceoryx_dds" TOOLCHAIN_FILE="" From 029a4b7f18e060369e189fa74957ddbd72559829 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Tue, 30 Mar 2021 21:19:30 +0200 Subject: [PATCH 046/127] iox-#482 Add review notes Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/README.md | 25 ++++++++++++------- .../icecubetray/run_icecubetray.sh | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/icecubetray/README.md index b95bdfa1ba3..066c4071eac 100644 --- a/iceoryx_examples/icecubetray/README.md +++ b/iceoryx_examples/icecubetray/README.md @@ -23,14 +23,15 @@ It provides a custom RouDi, a radar and a display application. The segments are configured as depicted below: +#### Positive Scenario ``` +-----------------------+ +--------------------+ | | +---------------------+ -| Radar App | | Privileged Shared | | Some Other App | -| @perception | | Memory Segment | | @perception | +| Radar App | | Privileged Shared | | Preprocessing App | +| @perception | | Memory Segment | | @perception | | | publish | | subscribe | | -| | -------> | r: unprivileged | ------> | | -| | | w: privileged | | | +| | -------> | r group: unprivileged | ------> | | +| | | w group: privileged | | | | | | | | | | | | | <------ | | | | | | publish | | @@ -45,11 +46,11 @@ The segments are configured as depicted below: \ / +-----------------------+ | | +---------------------+ - | Infotainment Shared | | Display App | - | Memory Segment | publish | @infotainment | + | Infotainment Shared | | Display App | + | Memory Segment | publish | @infotainment | | | <------ | | - | r: infotainment | | | - | w: infotainment | | | + | r group: infotainment | | | + | w group: infotainment | | | | | | | | | | | | | | | @@ -58,11 +59,17 @@ The segments are configured as depicted below: ``` It's advised to create per writer group only one shared memory segement (e.g. not two segements with `w: infotainment`). -In this case it wouldn't be possible to control which segment will be used. +In this case it wouldn't be possible to control which segment will be used. (=> add a test for that) !!! note Note the shared memory managment segment is always available for everyone to **read** and **write** +#### Negative Scenario + +Start preprocessing app with 'dummy' user, should not get not_null segfault in RouDi, but something like "Access denied, no shared memory payload segment available" + + + ### Radar App ### Display App diff --git a/iceoryx_examples/icecubetray/run_icecubetray.sh b/iceoryx_examples/icecubetray/run_icecubetray.sh index 917cf1c85f7..88f80c03402 100644 --- a/iceoryx_examples/icecubetray/run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/run_icecubetray.sh @@ -15,7 +15,7 @@ sudo useradd -M roudi # Assign users to group and disable login sudo usermod -L perception -a -G privileged -G iceoryx sudo usermod -L infotainment -a -G unprivileged -G iceoryx -sudo usermod -L roudi -a -G unprivileged -G iceoryx +sudo usermod -L roudi -a -G iceoryx # If you're using e.g Yocto or QNX refer to the manual on how to set up groups and users From 1f89556a0dcc774f8d3551cdb4ccebcb63350e95 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Wed, 7 Apr 2021 15:27:50 +0200 Subject: [PATCH 047/127] iox-#482 Add info about CAP_KILL Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/README.md | 4 +++- iceoryx_examples/icecubetray/run_icecubetray.sh | 14 +++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/icecubetray/README.md index 066c4071eac..daa20e2325d 100644 --- a/iceoryx_examples/icecubetray/README.md +++ b/iceoryx_examples/icecubetray/README.md @@ -11,7 +11,8 @@ It provides a custom RouDi, a radar and a display application. ## Code walkthrough - +The user _roudi_ does not need root access rights. However, it needs _CAP\_KILL_ capability or similar to send a +_SIGKILL_ signal to the apps in case RouDi is shutdown. | Users | privileged group | unprivileged group | infotainment group | iceoryx group | |--------------|:----------------:|:------------------:|:------------------:|:------------------:| @@ -24,6 +25,7 @@ It provides a custom RouDi, a radar and a display application. The segments are configured as depicted below: #### Positive Scenario + ``` +-----------------------+ +--------------------+ | | +---------------------+ diff --git a/iceoryx_examples/icecubetray/run_icecubetray.sh b/iceoryx_examples/icecubetray/run_icecubetray.sh index 88f80c03402..aa02d5c53bf 100644 --- a/iceoryx_examples/icecubetray/run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/run_icecubetray.sh @@ -13,11 +13,15 @@ sudo useradd -M infotainment sudo useradd -M roudi # Assign users to group and disable login -sudo usermod -L perception -a -G privileged -G iceoryx -sudo usermod -L infotainment -a -G unprivileged -G iceoryx +sudo usermod -L perception -a -G privileged +sudo usermod -L perception -a -G iceoryx +sudo usermod -L infotainment -a -G unprivileged +sudo usermod -L infotainment -a -G iceoryx sudo usermod -L roudi -a -G iceoryx -# If you're using e.g Yocto or QNX refer to the manual on how to set up groups and users +# If you're using e.g Yocto or QNX refer to the manual on how to set up groups, users and permissions + +sudo setcap cap_kill=ep ./build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments # Start custom RouDi in 'iceoryx' group sudo -u roudi -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments @@ -25,5 +29,5 @@ sudo -u roudi -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cp # Start perception app as 'perception' user sudo -u perception -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-radar -# Start algorithm app as 'infotainment' user -sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-algorithm +# Start display app as 'infotainment' user +sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-display From 0e692a468a102301ca1ddf4d674c86e7c160d047 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Wed, 7 Apr 2021 19:58:57 +0200 Subject: [PATCH 048/127] iox-#482 Continue writing the icecubetray readme Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/README.md | 73 ++++++++++++------- .../icecubetray/run_icecubetray.sh | 2 + 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/icecubetray/README.md index daa20e2325d..8896bda384e 100644 --- a/iceoryx_examples/icecubetray/README.md +++ b/iceoryx_examples/icecubetray/README.md @@ -11,32 +11,29 @@ It provides a custom RouDi, a radar and a display application. ## Code walkthrough -The user _roudi_ does not need root access rights. However, it needs _CAP\_KILL_ capability or similar to send a -_SIGKILL_ signal to the apps in case RouDi is shutdown. +The user _roudi_ does not need root access rights. However, it needs _CAP\_KILL_ capability or something similar on +other POSIX operating system. RouDi needs to be able to send a _SIGKILL_ signal to the apps in case RouDi is shutdown. | Users | privileged group | unprivileged group | infotainment group | iceoryx group | |--------------|:----------------:|:------------------:|:------------------:|:------------------:| | perception | X | | | X | | infotainment | | X | X | X | | roudi | | | | X | +| notallowed | | | | X | -### RouDi with static shared memory segments - -The segments are configured as depicted below: - -#### Positive Scenario +### Overview over the Apps and Shared Memory Segments ``` +-----------------------+ +--------------------+ | | +---------------------+ -| Radar App | | Privileged Shared | | Preprocessing App | -| @perception | | Memory Segment | | @perception | -| | publish | | subscribe | | -| | -------> | r group: unprivileged | ------> | | -| | | w group: privileged | | | -| | | | | | -| | | | <------ | | -| | | | publish | | +| Radar App | | Privileged Shared | | Cheeky App | +| @perception | | Memory Segment | | @notallowed | +| | publish | | subscribe | # # | +| # | -------> | r group: unprivileged | ------> | # # | +| # | | w group: privileged | | # # | +| # | | | | # # | +| # # | | | <------ | # # | +| # # | | | publish | # # | +--------------------+ | | +---------------------+ +-----------------------+ | @@ -51,27 +48,47 @@ The segments are configured as depicted below: | Infotainment Shared | | Display App | | Memory Segment | publish | @infotainment | | | <------ | | - | r group: infotainment | | | - | w group: infotainment | | | - | | | | - | | | | - | | | | + | r group: infotainment | | # | + | w group: infotainment | | # | + | | | # | + | | | # # | + | | | # # | | | +---------------------+ +-----------------------+ ``` -It's advised to create per writer group only one shared memory segement (e.g. not two segements with `w: infotainment`). -In this case it wouldn't be possible to control which segment will be used. (=> add a test for that) +#### RouDi and Apps -!!! note - Note the shared memory managment segment is always available for everyone to **read** and **write** -#### Negative Scenario +##### Working setup + +RouDi is built with two static shared memory segments _infotainment_ and _privileged_. The access rights of the segments are configured as depicted in the graphic above. + +The radar app is started with the user _perception_ and is sending data into the _privileged_ shared memory segment. + +The display app is started with the user _infotainment_. It reads the topic `{"Radar", "FrontLeft", "Object"}` from the privileged segement and forwards it as a slighty modified topic `{"Radar", "HMI-Display", "Object"}`. Because the user _infotainment_ only has write access to the infotainment segment, the data is written to this one. -Start preprocessing app with 'dummy' user, should not get not_null segfault in RouDi, but something like "Access denied, no shared memory payload segment available" +!!! hint + It's advised to create per writer group only one shared memory segement (e.g. not two segements with `w: infotainment`). + In this case it wouldn't be possible to control which segment will be used. (=> add a test for that) +The shared memory segments can be found under `/dev/shm` + +``` +moss@reynholm:~$ ls -al /dev/shm +total 60268 +drwxrwxrwt 2 root root 100 Apr 7 19:54 . +drwxr-xr-x 6 root root 460 Apr 6 15:53 .. +-rw-rw---- 1 roudi iceoryx 61383328 Apr 7 19:24 iceoryx_mgmt +-rw-rw----+ 1 roudi iceoryx 160000 Apr 7 19:24 infotainment +-rw-rw----+ 1 roudi iceoryx 160000 Apr 7 19:24 privileged +``` + +!!! note + Note the shared memory managment segment is always available for everyone to **read** and **write** +##### Not-working setup -### Radar App +The cheeky app is started with the user __notallowed_. It has neither write nor read access to any shared memory segment. Hence, RouDi will print a warning in this case. -### Display App +(Start preprocessing app with 'notallowed' user, should not get not_null segfault in RouDi, but something like "Access denied, no shared memory payload segment available") diff --git a/iceoryx_examples/icecubetray/run_icecubetray.sh b/iceoryx_examples/icecubetray/run_icecubetray.sh index aa02d5c53bf..cd87aa717f0 100644 --- a/iceoryx_examples/icecubetray/run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/run_icecubetray.sh @@ -21,6 +21,8 @@ sudo usermod -L roudi -a -G iceoryx # If you're using e.g Yocto or QNX refer to the manual on how to set up groups, users and permissions + +# Allow RouDi to send SIGKILL to other apps sudo setcap cap_kill=ep ./build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments # Start custom RouDi in 'iceoryx' group From 07070ad46874acf1c643e3d929b8164ae4ad033d Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Thu, 8 Apr 2021 21:13:01 +0200 Subject: [PATCH 049/127] iox-#482 Avoid segfault when user is not found in writerGroup and refactor ProcessManager::addProcess() to store the posix::User Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/CMakeLists.txt | 9 ++- iceoryx_examples/icecubetray/README.md | 6 +- .../icecubetray/iox_cheeky_app.cpp | 79 +++++++++++++++++++ .../icecubetray/run_icecubetray.sh | 4 +- .../internal/mepoo/segment_manager.hpp | 4 +- .../iceoryx_posh/internal/roudi/process.hpp | 9 +-- .../internal/roudi/process_manager.hpp | 9 +-- .../internal/runtime/ipc_interface_base.hpp | 2 + iceoryx_posh/source/roudi/process.cpp | 15 +--- iceoryx_posh/source/roudi/process_manager.cpp | 63 ++++++++------- .../source/runtime/ipc_runtime_interface.cpp | 8 ++ .../test/integrationtests/test_posh_mepoo.cpp | 3 +- .../test_mepoo_segment_management.cpp | 8 +- .../moduletests/test_roudi_portmanager.cpp | 6 +- .../test/moduletests/test_roudi_process.cpp | 29 +++---- .../test_roudi_process_manager.cpp | 14 ++-- .../error_handling/error_handling.hpp | 1 + 17 files changed, 181 insertions(+), 88 deletions(-) create mode 100644 iceoryx_examples/icecubetray/iox_cheeky_app.cpp diff --git a/iceoryx_examples/icecubetray/CMakeLists.txt b/iceoryx_examples/icecubetray/CMakeLists.txt index 923f9d96b6d..53646962fec 100644 --- a/iceoryx_examples/icecubetray/CMakeLists.txt +++ b/iceoryx_examples/icecubetray/CMakeLists.txt @@ -34,15 +34,20 @@ target_link_libraries(iox-cpp-radar ) target_compile_options(iox-cpp-radar PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) - add_executable(iox-cpp-display iox_display_app.cpp) - target_link_libraries(iox-cpp-display iceoryx_posh::iceoryx_posh ) target_compile_options(iox-cpp-display PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) +add_executable(iox-cpp-cheeky iox_cheeky_app.cpp) + +target_link_libraries(iox-cpp-cheeky + iceoryx_posh::iceoryx_posh +) +target_compile_options(iox-cpp-cheeky PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) + add_executable(iox-cpp-roudi-static-segments roudi_main_static_segements.cpp ) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/icecubetray/README.md index 8896bda384e..2a4e4818fd1 100644 --- a/iceoryx_examples/icecubetray/README.md +++ b/iceoryx_examples/icecubetray/README.md @@ -2,9 +2,13 @@ ## Introduction -This example demonstrates how access rights can be applied to shared memory segments. +This example demonstrates how access rights can be applied to shared memory segments on Linux-based operating system. It provides a custom RouDi, a radar and a display application. +!!! hint + The access rights feature is only supported on Linux-based operating systems + + ## Expected output diff --git a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp new file mode 100644 index 00000000000..bfb4aba29e5 --- /dev/null +++ b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "topic_data.hpp" + +#include "iceoryx_posh/popo/publisher.hpp" +#include "iceoryx_posh/popo/subscriber.hpp" +#include "iceoryx_posh/runtime/posh_runtime.hpp" +#include "iceoryx_utils/posix_wrapper/signal_handler.hpp" + +#include + +bool killswitch = false; +constexpr char APP_NAME[] = "iox-cpp-cheeky"; + +static void sigHandler(int f_sig [[gnu::unused]]) +{ + // caught SIGINT or SIGTERM, now exit gracefully + killswitch = true; +} + +int main() +{ + // register sigHandler + auto signalIntGuard = iox::posix::registerSignalHandler(iox::posix::Signal::INT, sigHandler); + auto signalTermGuard = iox::posix::registerSignalHandler(iox::posix::Signal::TERM, sigHandler); + + // initialize runtime + iox::runtime::PoshRuntime::initRuntime(APP_NAME); + + // initialized subscriber + iox::popo::Subscriber subscriber({"Radar", "FrontLeft", "Object"}); + iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); + + // run until interrupted by Ctrl-C + while (!killswitch) + { + auto takeResult = subscriber.take(); + + if (!takeResult.has_error()) + { + publisher.loan().and_then([&](auto& sample) { + sample->x = takeResult.value()->x; + sample->y = takeResult.value()->y; + sample->z = takeResult.value()->z; + std::cout << APP_NAME << " sending value: " << takeResult.value()->x << std::endl; + sample.publish(); + }); + } + else + { + if (takeResult.get_error() == iox::popo::ChunkReceiveResult::NO_CHUNK_AVAILABLE) + { + std::cout << "No chunk available." << std::endl; + } + else + { + std::cout << "Error receiving chunk." << std::endl; + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + return (EXIT_SUCCESS); +} diff --git a/iceoryx_examples/icecubetray/run_icecubetray.sh b/iceoryx_examples/icecubetray/run_icecubetray.sh index cd87aa717f0..0093aa0bae1 100644 --- a/iceoryx_examples/icecubetray/run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/run_icecubetray.sh @@ -11,13 +11,15 @@ sudo groupadd -f iceoryx sudo useradd -M perception sudo useradd -M infotainment sudo useradd -M roudi +sudo useradd -M notallowed # Assign users to group and disable login sudo usermod -L perception -a -G privileged -sudo usermod -L perception -a -G iceoryx sudo usermod -L infotainment -a -G unprivileged +sudo usermod -L perception -a -G iceoryx sudo usermod -L infotainment -a -G iceoryx sudo usermod -L roudi -a -G iceoryx +sudo usermod -L notallowed -a -G iceoryx # If you're using e.g Yocto or QNX refer to the manual on how to set up groups, users and permissions diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp index 69aa84c6c0c..ac25e701de6 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp @@ -21,6 +21,7 @@ #include "iceoryx_posh/internal/mepoo/memory_manager.hpp" #include "iceoryx_posh/internal/mepoo/mepoo_segment.hpp" #include "iceoryx_posh/mepoo/segment_config.hpp" +#include "iceoryx_utils/cxx/optional.hpp" #include "iceoryx_utils/cxx/string.hpp" #include "iceoryx_utils/cxx/vector.hpp" #include "iceoryx_utils/internal/posix_wrapper/shared_memory_object/allocator.hpp" @@ -78,7 +79,7 @@ class SegmentManager struct SegmentUserInformation { - MemoryManager* m_memoryManager; + cxx::optional m_memoryManager; uint64_t m_segmentID; }; @@ -86,6 +87,7 @@ class SegmentManager SegmentMappingContainer getSegmentMappings(posix::PosixUser f_user); SegmentUserInformation getSegmentInformationForUser(posix::PosixUser f_user); + bool doesUserHaveAccessToSegment() noexcept; static uint64_t requiredManagementMemorySize(const SegmentConfig& f_config); static uint64_t requiredChunkMemorySize(const SegmentConfig& f_config); diff --git a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp index d23fe994a84..64a9f591ad5 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp @@ -49,9 +49,8 @@ class Process /// @param [in] sessionId is an ID generated by RouDi to prevent sending outdated IPC channel transmission Process(const RuntimeName_t& name, const uint32_t pid, - mepoo::MemoryManager& payloadDataSegmentMemoryManager, + posix::PosixUser user, const bool isMonitored, - const uint64_t dataSegmentId, const uint64_t sessionId) noexcept; Process(const Process& other) = delete; @@ -75,8 +74,7 @@ class Process mepoo::TimePointNs_t getTimestamp() noexcept; - mepoo::MemoryManager& getpayloadDataSegmentMemoryManager() const noexcept; - uint64_t getDataSegmentId() const noexcept; + posix::PosixUser getUser() const noexcept; bool isMonitored() const noexcept; @@ -84,9 +82,8 @@ class Process const uint32_t m_pid{0U}; runtime::IpcInterfaceUser m_ipcChannel; mepoo::TimePointNs_t m_timestamp; - mepoo::MemoryManager& m_payloadDataSegmentMemoryManager; + posix::PosixUser m_user; bool m_isMonitored{true}; - uint64_t m_dataSegmentId{0U}; std::atomic m_sessionId{0U}; }; diff --git a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp index d1c7b1da2e7..dce99c6137d 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp @@ -150,20 +150,17 @@ class ProcessManager : public ProcessManagerInterface /// @param [in] name of the process; this is equal to the IPC channel name, which is used for communication /// @param [in] pid is the host system process id - /// @param [in] payloadDataSegmentMemoryManager is a pointer to the memory manager of the payload data segment for - /// this process + /// @param [in] payloadMemoryManager is a reference to the payload memory manager for this process /// @param [in] isMonitored indicates if the process should be monitored for being alive /// @param [in] transmissionTimestamp is an ID for the application to check for the expected response - /// @param [in] dataSegmentId is an identifier for the shm data segment /// @param [in] sessionId is an ID generated by RouDi to prevent sending outdated IPC channel transmission /// @param [in] versionInfo Version of iceoryx used /// @return Returns if the process could be added successfully. bool addProcess(const RuntimeName_t& name, const uint32_t pid, - cxx::not_null payloadDataSegmentMemoryManager, + posix::PosixUser user, const bool isMonitored, const int64_t transmissionTimestamp, - const uint64_t dataSegmentId, const uint64_t sessionId, const version::VersionInfo& versionInfo) noexcept; @@ -217,8 +214,6 @@ class ProcessManager : public ProcessManagerInterface rp::BaseRelativePointer::id_t m_mgmtSegmentId{rp::BaseRelativePointer::NULL_POINTER_ID}; ProcessList_t m_processList; ProcessIntrospectionType* m_processIntrospection{nullptr}; - /// @brief is currently used for the internal publisher/subscriber ports - mepoo::MemoryManager* m_memoryManagerOfCurrentProcess{nullptr}; version::CompatibilityCheckLevel m_compatibilityCheckLevel; }; diff --git a/iceoryx_posh/include/iceoryx_posh/internal/runtime/ipc_interface_base.hpp b/iceoryx_posh/include/iceoryx_posh/internal/runtime/ipc_interface_base.hpp index 818b6166e61..6a68fb9a9c0 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/runtime/ipc_interface_base.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/runtime/ipc_interface_base.hpp @@ -51,6 +51,8 @@ enum class IpcMessageType : int32_t NOTYPE = 0, REG, // register app REG_ACK, + REG_FAIL_RUNTIME_NAME_ALREADY_REGISTERED, + REG_FAIL_NO_WRITABLE_SHM_SEGMENT, CREATE_PUBLISHER, CREATE_PUBLISHER_ACK, CREATE_SUBSCRIBER, diff --git a/iceoryx_posh/source/roudi/process.cpp b/iceoryx_posh/source/roudi/process.cpp index d3756ed77bb..894d46f2148 100644 --- a/iceoryx_posh/source/roudi/process.cpp +++ b/iceoryx_posh/source/roudi/process.cpp @@ -27,16 +27,14 @@ namespace roudi { Process::Process(const RuntimeName_t& name, const uint32_t pid, - mepoo::MemoryManager& payloadDataSegmentMemoryManager, + posix::PosixUser user, const bool isMonitored, - const uint64_t dataSegmentId, const uint64_t sessionId) noexcept : m_pid(pid) , m_ipcChannel(name) , m_timestamp(mepoo::BaseClock_t::now()) - , m_payloadDataSegmentMemoryManager(payloadDataSegmentMemoryManager) + , m_user(user) , m_isMonitored(isMonitored) - , m_dataSegmentId(dataSegmentId) , m_sessionId(sessionId) { } @@ -76,14 +74,9 @@ mepoo::TimePointNs_t Process::getTimestamp() noexcept return m_timestamp; } -mepoo::MemoryManager& Process::getpayloadDataSegmentMemoryManager() const noexcept +posix::PosixUser Process::getUser() const noexcept { - return m_payloadDataSegmentMemoryManager; -} - -uint64_t Process::getDataSegmentId() const noexcept -{ - return m_dataSegmentId; + return m_user; } bool Process::isMonitored() const noexcept diff --git a/iceoryx_posh/source/roudi/process_manager.cpp b/iceoryx_posh/source/roudi/process_manager.cpp index 14f9bba0cdd..b9789213483 100644 --- a/iceoryx_posh/source/roudi/process_manager.cpp +++ b/iceoryx_posh/source/roudi/process_manager.cpp @@ -68,10 +68,6 @@ ProcessManager::ProcessManager(RouDiMemoryInterface& roudiMemoryInterface, std::terminate(); } m_mgmtSegmentId = maybeMgmtSegmentId.value(); - - auto currentUser = posix::PosixUser::getUserOfCurrentProcess(); - auto m_segmentInfo = m_segmentManager->getSegmentInformationForUser(currentUser); - m_memoryManagerOfCurrentProcess = m_segmentInfo.m_memoryManager; } void ProcessManager::handleProcessShutdownPreparationRequest(const RuntimeName_t& name) noexcept @@ -205,8 +201,6 @@ bool ProcessManager::registerProcess(const RuntimeName_t& name, const uint64_t sessionId, const version::VersionInfo& versionInfo) noexcept { - auto segmentInfo = m_segmentManager->getSegmentInformationForUser(user); - bool returnValue{false}; searchForProcessAndThen( @@ -234,27 +228,30 @@ bool ProcessManager::registerProcess(const RuntimeName_t& name, } else { - // try registration again, should succeed since removal was successful - returnValue = addProcess(name, - pid, - segmentInfo.m_memoryManager, - isMonitored, - transmissionTimestamp, - segmentInfo.m_segmentID, - sessionId, - versionInfo); + // process exists and is not monitored, we expect that the existing process crashed + LogDebug() << "Registering already existing application " << name; + + // remove the existing process and add the new process afterwards, we do not send ack to new process + constexpr TerminationFeedback terminationFeedback{TerminationFeedback::DO_NOT_SEND_ACK_TO_PROCESS}; + if (!searchForProcessAndRemoveIt(name, terminationFeedback)) + { + LogWarn() + << "Received REG from " << name + << ", but another application with this name is already registered and could not be removed"; + return; + } + else + { + LogDebug() << "Removed existing application " << name; + // try registration again, should succeed since removal was successful + returnValue = + addProcess(name, pid, user, isMonitored, transmissionTimestamp, sessionId, versionInfo); + } } }, [&]() { // process does not exist in list and can be added - returnValue = addProcess(name, - pid, - segmentInfo.m_memoryManager, - isMonitored, - transmissionTimestamp, - segmentInfo.m_segmentID, - sessionId, - versionInfo); + returnValue = addProcess(name, pid, user, isMonitored, transmissionTimestamp, sessionId, versionInfo); }); return returnValue; @@ -262,10 +259,9 @@ bool ProcessManager::registerProcess(const RuntimeName_t& name, bool ProcessManager::addProcess(const RuntimeName_t& name, const uint32_t pid, - cxx::not_null payloadDataSegmentMemoryManager, + posix::PosixUser user, const bool isMonitored, const int64_t transmissionTimestamp, - const uint64_t dataSegmentId, const uint64_t sessionId, const version::VersionInfo& versionInfo) noexcept { @@ -284,7 +280,7 @@ bool ProcessManager::addProcess(const RuntimeName_t& name, LogError() << "Could not register process '" << name << "' - too many processes"; return false; } - m_processList.emplace_back(name, pid, *payloadDataSegmentMemoryManager, isMonitored, dataSegmentId, sessionId); + m_processList.emplace_back(name, pid, user, isMonitored, sessionId); // send REG_ACK and BaseAddrString runtime::IpcMessage sendBuffer; @@ -531,8 +527,21 @@ void ProcessManager::addPublisherForProcess(const RuntimeName_t& name, searchForProcessAndThen( name, [&](Process& process) { // create a PublisherPort + auto segmentInfo = m_segmentManager->getSegmentInformationForUser(process.getUser()); + + if (!segmentInfo.m_memoryManager.has_value()) + { + // Tell the app not writable shared memory segment was found + runtime::IpcInterfaceUser ipcChannel{name}; + runtime::IpcMessage sendBuffer; + sendBuffer << runtime::IpcMessageTypeToString( + runtime::IpcMessageType::REG_FAIL_NO_WRITABLE_SHM_SEGMENT); + ipcChannel.send(sendBuffer); + return; + } + auto maybePublisher = m_portManager.acquirePublisherPortData( - service, publisherOptions, name, &process.getpayloadDataSegmentMemoryManager(), portConfigInfo); + service, publisherOptions, name, segmentInfo.m_memoryManager.value(), portConfigInfo); if (!maybePublisher.has_error()) { diff --git a/iceoryx_posh/source/runtime/ipc_runtime_interface.cpp b/iceoryx_posh/source/runtime/ipc_runtime_interface.cpp index 2d032b65612..e65f4d39cbb 100644 --- a/iceoryx_posh/source/runtime/ipc_runtime_interface.cpp +++ b/iceoryx_posh/source/runtime/ipc_runtime_interface.cpp @@ -246,6 +246,14 @@ IpcRuntimeInterface::RegAckResult IpcRuntimeInterface::waitForRegAck(int64_t tra LogWarn() << "Received a REG_ACK with an outdated timestamp!"; } } + /// @todo move this to CREATE_PUBLISHER answer + else if (stringToIpcMessageType(cmd.c_str()) == IpcMessageType::REG_FAIL_NO_WRITABLE_SHM_SEGMENT) + { + LogFatal() << "RouDi did not find a writable shared memory segment for the current user. Try " + "using another user or adapt RouDi's config."; + errorHandler(Error::kPOSH__RUNTIME_NO_WRITABLE_SHM_SEGMENT, nullptr, iox::ErrorLevel::FATAL); + break; + } else { LogError() << "Wrong response received " << receiveBuffer.getMessage(); diff --git a/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp b/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp index fec028194cc..9c73effc03c 100644 --- a/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp +++ b/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp @@ -252,7 +252,8 @@ class Mepoo_IntegrationTest : public Test auto memoryManager = m_roudiEnv->m_roudiApp->m_mempoolIntrospection.m_segmentManager ->getSegmentInformationForUser(currentUser.getName()) .m_memoryManager; - m_roudiEnv->m_roudiApp->m_mempoolIntrospection.copyMemPoolInfo(*memoryManager, mempoolInfo); + ASSERT_TRUE(memoryManager.has_value()); + m_roudiEnv->m_roudiApp->m_mempoolIntrospection.copyMemPoolInfo(*memoryManager.value(), mempoolInfo); // internally, the chunks are adjusted to the additional management information; // this needs to be subtracted to be able to compare to the configured sizes diff --git a/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp b/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp index 1e295837e8e..cb244d8439f 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp @@ -122,11 +122,11 @@ TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getSegmentMappingsEmpt TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getMemoryManagerForUserWithWriteUser)) { auto memoryManager = sut.getSegmentInformationForUser({"iox_roudi_test2"}).m_memoryManager; - ASSERT_THAT(memoryManager, Ne(nullptr)); - ASSERT_THAT(memoryManager->getNumberOfMemPools(), Eq(2u)); + ASSERT_TRUE(memoryManager.has_value()); + ASSERT_THAT(memoryManager.value()->getNumberOfMemPools(), Eq(2u)); - auto poolInfo1 = memoryManager->getMemPoolInfo(0); - auto poolInfo2 = memoryManager->getMemPoolInfo(1); + auto poolInfo1 = memoryManager.value()->getMemPoolInfo(0); + auto poolInfo2 = memoryManager.value()->getMemPoolInfo(1); EXPECT_THAT(poolInfo1.m_numChunks, Eq(5u)); EXPECT_THAT(poolInfo2.m_numChunks, Eq(7u)); } diff --git a/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp b/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp index 8f28e88f3ad..b538e8fa199 100644 --- a/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp +++ b/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp @@ -83,8 +83,10 @@ class PortManager_test : public Test m_portManager = new PortManagerTester(m_roudiMemoryManager); auto user = iox::posix::PosixUser::getUserOfCurrentProcess().getName(); - m_payloadDataSegmentMemoryManager = - m_roudiMemoryManager->segmentManager().value()->getSegmentInformationForUser(user).m_memoryManager; + auto segmentInfo = m_roudiMemoryManager->segmentManager().value()->getSegmentInformationForUser(user); + ASSERT_TRUE(segmentInfo.m_memoryManager.has_value()); + + m_payloadDataSegmentMemoryManager = segmentInfo.m_memoryManager.value(); // clearing the introspection, is not in d'tor -> SEGFAULT in delete sporadically m_portManager->stopPortIntrospection(); diff --git a/iceoryx_posh/test/moduletests/test_roudi_process.cpp b/iceoryx_posh/test/moduletests/test_roudi_process.cpp index 9d4386fc839..fea14f679ae 100644 --- a/iceoryx_posh/test/moduletests/test_roudi_process.cpp +++ b/iceoryx_posh/test/moduletests/test_roudi_process.cpp @@ -28,13 +28,14 @@ using namespace ::testing; using namespace iox::roudi; using namespace iox::popo; using namespace iox::runtime; +using namespace iox::posix; using ::testing::Return; class IpcInterfaceUser_Mock : public iox::roudi::Process { public: IpcInterfaceUser_Mock() - : iox::roudi::Process("TestProcess", 200, m_payloadDataSegmentMemoryManager, true, 0x654321, 255) + : iox::roudi::Process("TestProcess", 200, PosixUser("foo"), true, 255) { } MOCK_METHOD1(sendViaIpcChannel, void(IpcMessage)); @@ -46,7 +47,7 @@ class Process_test : public Test public: const iox::RuntimeName_t processname = {"TestProcess"}; pid_t pid{200U}; - iox::mepoo::MemoryManager payloadDataSegmentMemoryManager; + PosixUser user{"foo"}; bool isMonitored = true; const uint64_t dataSegmentId{0x654321U}; const uint64_t sessionId{255U}; @@ -55,40 +56,28 @@ class Process_test : public Test TEST_F(Process_test, getPid) { - Process roudiproc(processname, pid, payloadDataSegmentMemoryManager, isMonitored, dataSegmentId, sessionId); + Process roudiproc(processname, pid, user, isMonitored, sessionId); EXPECT_THAT(roudiproc.getPid(), Eq(pid)); } TEST_F(Process_test, getName) { - Process roudiproc(processname, pid, payloadDataSegmentMemoryManager, isMonitored, dataSegmentId, sessionId); + Process roudiproc(processname, pid, user, isMonitored, sessionId); EXPECT_THAT(roudiproc.getName(), Eq(std::string(processname))); } TEST_F(Process_test, isMonitored) { - Process roudiproc(processname, pid, payloadDataSegmentMemoryManager, isMonitored, dataSegmentId, sessionId); + Process roudiproc(processname, pid, user, isMonitored, sessionId); EXPECT_THAT(roudiproc.isMonitored(), Eq(isMonitored)); } -TEST_F(Process_test, getDataSegmentId) -{ - Process roudiproc(processname, pid, payloadDataSegmentMemoryManager, isMonitored, dataSegmentId, sessionId); - EXPECT_THAT(roudiproc.getDataSegmentId(), Eq(dataSegmentId)); -} - TEST_F(Process_test, getSessionId) { - Process roudiproc(processname, pid, payloadDataSegmentMemoryManager, isMonitored, dataSegmentId, sessionId); + Process roudiproc(processname, pid, user, isMonitored, sessionId); EXPECT_THAT(roudiproc.getSessionId(), Eq(sessionId)); } -TEST_F(Process_test, getpayloadDataSegmentMemoryManager) -{ - Process roudiproc(processname, pid, payloadDataSegmentMemoryManager, isMonitored, dataSegmentId, sessionId); - EXPECT_THAT(&roudiproc.getpayloadDataSegmentMemoryManager(), Eq(&payloadDataSegmentMemoryManager)); -} - TEST_F(Process_test, sendViaIpcChannelPass) { iox::runtime::IpcMessage data{"MESSAGE_NOT_SUPPORTED"}; @@ -107,7 +96,7 @@ TEST_F(Process_test, sendViaIpcChannelFail) EXPECT_THAT(errorLevel, Eq(iox::ErrorLevel::MODERATE)); }); - Process roudiproc(processname, pid, payloadDataSegmentMemoryManager, isMonitored, dataSegmentId, sessionId); + Process roudiproc(processname, pid, user, isMonitored, sessionId); roudiproc.sendViaIpcChannel(data); ASSERT_THAT(sendViaIpcChannelStatusFail.has_value(), Eq(true)); @@ -117,7 +106,7 @@ TEST_F(Process_test, sendViaIpcChannelFail) TEST_F(Process_test, TimeStamp) { auto timestmp = iox::mepoo::BaseClock_t::now(); - Process roudiproc(processname, pid, payloadDataSegmentMemoryManager, isMonitored, dataSegmentId, sessionId); + Process roudiproc(processname, pid, user, isMonitored, sessionId); roudiproc.setTimestamp(timestmp); EXPECT_THAT(roudiproc.getTimestamp(), Eq(timestmp)); } diff --git a/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp b/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp index 3fda8001629..ad4b0108e5c 100644 --- a/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp +++ b/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp @@ -124,14 +124,18 @@ TEST_F(ProcessManager_test, HandleProcessShutdownPreparationRequestWorks) auto payloadDataSegmentMemoryManager = m_roudiMemoryManager->segmentManager().value()->getSegmentInformationForUser(user).m_memoryManager; + ASSERT_TRUE(payloadDataSegmentMemoryManager.has_value()); + // get publisher and subscriber PublisherOptions publisherOptions{ 0U, iox::NodeName_t("node"), true, iox::popo::SubscriberTooSlowPolicy::WAIT_FOR_SUBSCRIBER}; - PublisherPortUser publisher( - m_portManager - ->acquirePublisherPortData( - {1U, 1U, 1U}, publisherOptions, m_processname, payloadDataSegmentMemoryManager, PortConfigInfo()) - .value()); + PublisherPortUser publisher(m_portManager + ->acquirePublisherPortData({1U, 1U, 1U}, + publisherOptions, + m_processname, + payloadDataSegmentMemoryManager.value(), + PortConfigInfo()) + .value()); ASSERT_TRUE(publisher.isOffered()); diff --git a/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp b/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp index 9b73a1cbcc3..59788edf525 100644 --- a/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp +++ b/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp @@ -52,6 +52,7 @@ namespace iox error(POSH__RUNTIME_ROUDI_CONDITION_VARIABLE_CREATION_UNDEFINED_BEHAVIOR) \ error(POSH__RUNTIME_ROUDI_EVENT_VARIABLE_CREATION_UNDEFINED_BEHAVIOR) \ error(POSH__RUNTIME_APP_WITH_SAME_RUNTIME_NAME_STILL_RUNNING) \ + error(POSH__RUNTIME_NO_WRITABLE_SHM_SEGMENT) \ error(POSH__PORT_MANAGER_PUBLISHERPORT_NOT_UNIQUE) \ error(POSH__MEMPOOL_POSSIBLE_DOUBLE_FREE) \ error(POSH__RECEIVERPORT_DELIVERYFIFO_OVERFLOW) \ From d6c10b95fa88dbf83815aecf0e3ef1f567cfa34f Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Fri, 9 Apr 2021 10:43:13 +0200 Subject: [PATCH 050/127] iox-#482 Move error handling of non-writable SHM segment to getMiddlewarePublisher() Signed-off-by: Simon Hoinkis --- .../include/iceoryx_posh/internal/mepoo/segment_manager.inl | 2 +- .../iceoryx_posh/internal/runtime/ipc_interface_base.hpp | 2 +- iceoryx_posh/source/roudi/process_manager.cpp | 5 +++-- iceoryx_posh/source/runtime/posh_runtime.cpp | 6 ++++++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl index 7fbdd82cae4..dd1b942d26a 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl @@ -125,7 +125,7 @@ SegmentManager::getSegmentInformationForUser(posix::PosixUser f_use { auto l_groupContainer = f_user.getGroups(); - SegmentUserInformation segmentInfo{nullptr, 0u}; + SegmentUserInformation segmentInfo{cxx::nullopt_t(), 0u}; // with the groups we can search for the writable segment of this user for (const auto& groupID : l_groupContainer) diff --git a/iceoryx_posh/include/iceoryx_posh/internal/runtime/ipc_interface_base.hpp b/iceoryx_posh/include/iceoryx_posh/internal/runtime/ipc_interface_base.hpp index 6a68fb9a9c0..dbcd986fd94 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/runtime/ipc_interface_base.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/runtime/ipc_interface_base.hpp @@ -51,7 +51,6 @@ enum class IpcMessageType : int32_t NOTYPE = 0, REG, // register app REG_ACK, - REG_FAIL_RUNTIME_NAME_ALREADY_REGISTERED, REG_FAIL_NO_WRITABLE_SHM_SEGMENT, CREATE_PUBLISHER, CREATE_PUBLISHER_ACK, @@ -90,6 +89,7 @@ enum class IpcMessageErrorType : int32_t /// A publisher could not be created unique NO_UNIQUE_CREATED, REQUEST_PUBLISHER_WRONG_IPC_MESSAGE_RESPONSE, + REQUEST_PUBLISHER_NO_WRITABLE_SHM_SEGMENT, REQUEST_SUBSCRIBER_WRONG_IPC_MESSAGE_RESPONSE, REQUEST_CONDITION_VARIABLE_WRONG_IPC_MESSAGE_RESPONSE, REQUEST_EVENT_VARIABLE_WRONG_IPC_MESSAGE_RESPONSE, diff --git a/iceoryx_posh/source/roudi/process_manager.cpp b/iceoryx_posh/source/roudi/process_manager.cpp index b9789213483..ee1641712e6 100644 --- a/iceoryx_posh/source/roudi/process_manager.cpp +++ b/iceoryx_posh/source/roudi/process_manager.cpp @@ -534,8 +534,9 @@ void ProcessManager::addPublisherForProcess(const RuntimeName_t& name, // Tell the app not writable shared memory segment was found runtime::IpcInterfaceUser ipcChannel{name}; runtime::IpcMessage sendBuffer; - sendBuffer << runtime::IpcMessageTypeToString( - runtime::IpcMessageType::REG_FAIL_NO_WRITABLE_SHM_SEGMENT); + sendBuffer << runtime::IpcMessageTypeToString(runtime::IpcMessageType::ERROR); + sendBuffer << runtime::IpcMessageErrorTypeToString( + runtime::IpcMessageErrorType::REQUEST_PUBLISHER_NO_WRITABLE_SHM_SEGMENT); ipcChannel.send(sendBuffer); return; } diff --git a/iceoryx_posh/source/runtime/posh_runtime.cpp b/iceoryx_posh/source/runtime/posh_runtime.cpp index 58bae8e1825..f6590f77ce9 100644 --- a/iceoryx_posh/source/runtime/posh_runtime.cpp +++ b/iceoryx_posh/source/runtime/posh_runtime.cpp @@ -224,6 +224,12 @@ PublisherPortUserType::MemberType_t* PoshRuntime::getMiddlewarePublisher(const c nullptr, iox::ErrorLevel::SEVERE); break; + case IpcMessageErrorType::REQUEST_PUBLISHER_NO_WRITABLE_SHM_SEGMENT: + LogWarn() << "Service '" << service.operator cxx::Serialization().toString() + << "' could not be created. RouDi did not find a writable shared memory segment for the current " + "user. Try using another user or adapt RouDi's config."; + errorHandler(Error::kPOSH__RUNTIME_NO_WRITABLE_SHM_SEGMENT, nullptr, iox::ErrorLevel::SEVERE); + break; default: LogWarn() << "Undefined behavior occurred while creating service '" << service.operator cxx::Serialization().toString() << "'."; From 6f049a720a8ef5a8f9459f9ca8c0ca58df9373be Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Fri, 9 Apr 2021 11:27:43 +0200 Subject: [PATCH 051/127] iox-#482 Extend icecubetray documentation Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/README.md | 37 ++++++++++++++++--- .../icecubetray/iox_cheeky_app.cpp | 37 +++---------------- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/icecubetray/README.md index 2a4e4818fd1..f26e8a0f73f 100644 --- a/iceoryx_examples/icecubetray/README.md +++ b/iceoryx_examples/icecubetray/README.md @@ -2,20 +2,19 @@ ## Introduction -This example demonstrates how access rights can be applied to shared memory segments on Linux-based operating system. +This example demonstrates how access rights can be applied to shared memory segments. It provides a custom RouDi, a radar and a display application. !!! hint The access rights feature is only supported on Linux-based operating systems - ## Expected output ## Code walkthrough -The user _roudi_ does not need root access rights. However, it needs _CAP\_KILL_ capability or something similar on +The user _roudi_ does not need root access rights. However, it needs _CAP\_KILL_ capability or similar rights on other POSIX operating system. RouDi needs to be able to send a _SIGKILL_ signal to the apps in case RouDi is shutdown. | Users | privileged group | unprivileged group | infotainment group | iceoryx group | @@ -63,14 +62,30 @@ other POSIX operating system. RouDi needs to be able to send a _SIGKILL_ signal #### RouDi and Apps - ##### Working setup RouDi is built with two static shared memory segments _infotainment_ and _privileged_. The access rights of the segments are configured as depicted in the graphic above. +The `roudiConfig` is composed of a memory pool config called `mepooConfig`. When the segement is created, one needs to +specific the reader (first string), write group (second string) as well as the `mepooConfig` (last parameter). + +```cpp +iox::RouDiConfig_t roudiConfig; + +// Create Mempool Config +iox::mepoo::MePooConfig mepooConfig; + +// We only send very small data, just one mempool per segment +mepooConfig.addMemPool({128, 1000}); + +/// Create an Entry for a new Shared Memory Segment from the MempoolConfig and add it to the RouDiConfig +roudiConfig.m_sharedMemorySegments.push_back({"unprivileged", "privileged", mepooConfig}); +roudiConfig.m_sharedMemorySegments.push_back({"infotainment", "infotainment", mepooConfig}); +``` + The radar app is started with the user _perception_ and is sending data into the _privileged_ shared memory segment. -The display app is started with the user _infotainment_. It reads the topic `{"Radar", "FrontLeft", "Object"}` from the privileged segement and forwards it as a slighty modified topic `{"Radar", "HMI-Display", "Object"}`. Because the user _infotainment_ only has write access to the infotainment segment, the data is written to this one. +The display app is started with the user _infotainment_. It reads the topic `{"Radar", "FrontLeft", "Object"}` from the privileged segement and forwards it as a slighty modified topic `{"Radar", "HMI-Display", "Object"}`. Because the user _infotainment_ only has write access to the infotainment segment, the data is written to this segment. !!! hint It's advised to create per writer group only one shared memory segement (e.g. not two segements with `w: infotainment`). @@ -95,4 +110,14 @@ drwxr-xr-x 6 root root 460 Apr 6 15:53 .. The cheeky app is started with the user __notallowed_. It has neither write nor read access to any shared memory segment. Hence, RouDi will print a warning in this case. -(Start preprocessing app with 'notallowed' user, should not get not_null segfault in RouDi, but something like "Access denied, no shared memory payload segment available") +Despite having no read access, subscriber can still be created. In this case no data will ever arrive. + +```cpp +iox::popo::Subscriber subscriber({"Radar", "FrontLeft", "Object"}); +``` + +When creating a publisher, no valid corresponding shared memory object can be created by RouDi. Hence, an error will be printed and the cheeky app will stop. + +```cpp +iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); +``` diff --git a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp index bfb4aba29e5..43bc1e3bb0d 100644 --- a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp +++ b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp @@ -41,39 +41,12 @@ int main() // initialize runtime iox::runtime::PoshRuntime::initRuntime(APP_NAME); - // initialized subscriber + // Subscriber can be create without any readable shared memory segment, in this case no data will ever arrive iox::popo::Subscriber subscriber({"Radar", "FrontLeft", "Object"}); - iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); - - // run until interrupted by Ctrl-C - while (!killswitch) - { - auto takeResult = subscriber.take(); - if (!takeResult.has_error()) - { - publisher.loan().and_then([&](auto& sample) { - sample->x = takeResult.value()->x; - sample->y = takeResult.value()->y; - sample->z = takeResult.value()->z; - std::cout << APP_NAME << " sending value: " << takeResult.value()->x << std::endl; - sample.publish(); - }); - } - else - { - if (takeResult.get_error() == iox::popo::ChunkReceiveResult::NO_CHUNK_AVAILABLE) - { - std::cout << "No chunk available." << std::endl; - } - else - { - std::cout << "Error receiving chunk." << std::endl; - } - } - - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } + // When starting this app with the user 'notallowed', the publisher object can't be initalised correctly because + // 'notallowed' does not have write access. + iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); - return (EXIT_SUCCESS); + return (EXIT_FAILURE); } From 82dbecac25de9efc9894d68b0d69b64e0dde810f Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Fri, 9 Apr 2021 11:29:33 +0200 Subject: [PATCH 052/127] iox-#482 Add cheeky app to bash script Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/run_icecubetray.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iceoryx_examples/icecubetray/run_icecubetray.sh b/iceoryx_examples/icecubetray/run_icecubetray.sh index 0093aa0bae1..76858bcbdd0 100644 --- a/iceoryx_examples/icecubetray/run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/run_icecubetray.sh @@ -35,3 +35,6 @@ sudo -u perception -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/i # Start display app as 'infotainment' user sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-display + +# Start cheeky app as 'notallowed' user +sudo -u notallowed -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-cheeky From b58be0166a6ce56592910554e33f30a3554c672c Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Fri, 9 Apr 2021 15:06:04 +0200 Subject: [PATCH 053/127] iox-#482 noexcept all the things and fix temporary IPCChannel Signed-off-by: Simon Hoinkis --- .../icecubetray/iox_cheeky_app.cpp | 4 ++-- .../internal/mepoo/segment_manager.hpp | 19 +++++++++---------- .../internal/mepoo/segment_manager.inl | 18 +++++++++--------- iceoryx_posh/source/roudi/process_manager.cpp | 5 ++--- iceoryx_posh/source/runtime/posh_runtime.cpp | 2 +- .../test/integrationtests/test_posh_mepoo.cpp | 2 +- .../test_mepoo_segment_management.cpp | 6 +++--- .../moduletests/test_roudi_portmanager.cpp | 2 +- 8 files changed, 28 insertions(+), 30 deletions(-) diff --git a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp index 43bc1e3bb0d..9f9184986cb 100644 --- a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp +++ b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp @@ -41,11 +41,11 @@ int main() // initialize runtime iox::runtime::PoshRuntime::initRuntime(APP_NAME); - // Subscriber can be create without any readable shared memory segment, in this case no data will ever arrive + // Subscribers can be create without any readable shared memory segment, in this case no data will ever arrive iox::popo::Subscriber subscriber({"Radar", "FrontLeft", "Object"}); // When starting this app with the user 'notallowed', the publisher object can't be initalised correctly because - // 'notallowed' does not have write access. + // 'notallowed' does not have write access iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); return (EXIT_FAILURE); diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp index ac25e701de6..a05d21c4247 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp @@ -41,8 +41,8 @@ template > class SegmentManager { public: - SegmentManager(const SegmentConfig& f_segmentConfig, posix::Allocator* f_managementAllocator); - ~SegmentManager() = default; + SegmentManager(const SegmentConfig& f_segmentConfig, posix::Allocator* f_managementAllocator) noexcept; + ~SegmentManager() noexcept = default; SegmentManager(const SegmentManager& rhs) = delete; SegmentManager(SegmentManager&& rhs) = delete; @@ -58,7 +58,7 @@ class SegmentManager uint64_t size, bool isWritable, uint64_t segmentId, - const iox::mepoo::MemoryInfo& memoryInfo = iox::mepoo::MemoryInfo()) + const iox::mepoo::MemoryInfo& memoryInfo = iox::mepoo::MemoryInfo()) noexcept : m_sharedMemoryName(sharedMemoryName) , m_startAddress(startAddress) , m_size(size) @@ -85,16 +85,15 @@ class SegmentManager using SegmentMappingContainer = cxx::vector; - SegmentMappingContainer getSegmentMappings(posix::PosixUser f_user); - SegmentUserInformation getSegmentInformationForUser(posix::PosixUser f_user); - bool doesUserHaveAccessToSegment() noexcept; + SegmentMappingContainer getSegmentMappings(posix::PosixUser f_user) noexcept; + SegmentUserInformation getSegmentInformationWithWriteAccessForUser(posix::PosixUser f_user) noexcept; - static uint64_t requiredManagementMemorySize(const SegmentConfig& f_config); - static uint64_t requiredChunkMemorySize(const SegmentConfig& f_config); - static uint64_t requiredFullMemorySize(const SegmentConfig& f_config); + static uint64_t requiredManagementMemorySize(const SegmentConfig& f_config) noexcept; + static uint64_t requiredChunkMemorySize(const SegmentConfig& f_config) noexcept; + static uint64_t requiredFullMemorySize(const SegmentConfig& f_config) noexcept; private: - bool createSegment(const SegmentConfig::SegmentEntry& f_segmentEntry); + bool createSegment(const SegmentConfig::SegmentEntry& f_segmentEntry) noexcept; private: template diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl index dd1b942d26a..f6437fdc41f 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl @@ -28,7 +28,7 @@ namespace mepoo { template inline SegmentManager::SegmentManager(const SegmentConfig& f_segmentConfig, - posix::Allocator* f_managementAllocator) + posix::Allocator* f_managementAllocator) noexcept : m_managementAllocator(f_managementAllocator) { for (const auto& segmentEntry : f_segmentConfig.m_sharedMemorySegments) @@ -38,7 +38,7 @@ inline SegmentManager::SegmentManager(const SegmentConfig& f_segmen } template -inline bool SegmentManager::createSegment(const SegmentConfig::SegmentEntry& f_segmentEntry) +inline bool SegmentManager::createSegment(const SegmentConfig::SegmentEntry& f_segmentEntry) noexcept { if (m_segmentContainer.size() < m_segmentContainer.capacity()) { @@ -60,7 +60,7 @@ inline bool SegmentManager::createSegment(const SegmentConfig::Segm template inline typename SegmentManager::SegmentMappingContainer -SegmentManager::getSegmentMappings(posix::PosixUser f_user) +SegmentManager::getSegmentMappings(posix::PosixUser f_user) noexcept { // get all the groups the user is in auto l_groupContainer = f_user.getGroups(); @@ -121,14 +121,14 @@ SegmentManager::getSegmentMappings(posix::PosixUser f_user) template inline typename SegmentManager::SegmentUserInformation -SegmentManager::getSegmentInformationForUser(posix::PosixUser f_user) +SegmentManager::getSegmentInformationWithWriteAccessForUser(posix::PosixUser user) noexcept { - auto l_groupContainer = f_user.getGroups(); + auto groupContainer = user.getGroups(); SegmentUserInformation segmentInfo{cxx::nullopt_t(), 0u}; // with the groups we can search for the writable segment of this user - for (const auto& groupID : l_groupContainer) + for (const auto& groupID : groupContainer) { for (auto& segment : m_segmentContainer) { @@ -145,7 +145,7 @@ SegmentManager::getSegmentInformationForUser(posix::PosixUser f_use } template -uint64_t SegmentManager::requiredManagementMemorySize(const SegmentConfig& f_config) +uint64_t SegmentManager::requiredManagementMemorySize(const SegmentConfig& f_config) noexcept { uint64_t memorySize{0u}; for (auto segment : f_config.m_sharedMemorySegments) @@ -156,7 +156,7 @@ uint64_t SegmentManager::requiredManagementMemorySize(const Segment } template -uint64_t SegmentManager::requiredChunkMemorySize(const SegmentConfig& f_config) +uint64_t SegmentManager::requiredChunkMemorySize(const SegmentConfig& f_config) noexcept { uint64_t memorySize{0u}; for (auto segment : f_config.m_sharedMemorySegments) @@ -167,7 +167,7 @@ uint64_t SegmentManager::requiredChunkMemorySize(const SegmentConfi } template -uint64_t SegmentManager::requiredFullMemorySize(const SegmentConfig& f_config) +uint64_t SegmentManager::requiredFullMemorySize(const SegmentConfig& f_config) noexcept { return requiredManagementMemorySize(f_config) + requiredChunkMemorySize(f_config); } diff --git a/iceoryx_posh/source/roudi/process_manager.cpp b/iceoryx_posh/source/roudi/process_manager.cpp index ee1641712e6..70fc0fad675 100644 --- a/iceoryx_posh/source/roudi/process_manager.cpp +++ b/iceoryx_posh/source/roudi/process_manager.cpp @@ -527,17 +527,16 @@ void ProcessManager::addPublisherForProcess(const RuntimeName_t& name, searchForProcessAndThen( name, [&](Process& process) { // create a PublisherPort - auto segmentInfo = m_segmentManager->getSegmentInformationForUser(process.getUser()); + auto segmentInfo = m_segmentManager->getSegmentInformationWithWriteAccessForUser(process.getUser()); if (!segmentInfo.m_memoryManager.has_value()) { // Tell the app not writable shared memory segment was found - runtime::IpcInterfaceUser ipcChannel{name}; runtime::IpcMessage sendBuffer; sendBuffer << runtime::IpcMessageTypeToString(runtime::IpcMessageType::ERROR); sendBuffer << runtime::IpcMessageErrorTypeToString( runtime::IpcMessageErrorType::REQUEST_PUBLISHER_NO_WRITABLE_SHM_SEGMENT); - ipcChannel.send(sendBuffer); + process.sendViaIpcChannel(sendBuffer); return; } diff --git a/iceoryx_posh/source/runtime/posh_runtime.cpp b/iceoryx_posh/source/runtime/posh_runtime.cpp index f6590f77ce9..81a24e63b95 100644 --- a/iceoryx_posh/source/runtime/posh_runtime.cpp +++ b/iceoryx_posh/source/runtime/posh_runtime.cpp @@ -226,7 +226,7 @@ PublisherPortUserType::MemberType_t* PoshRuntime::getMiddlewarePublisher(const c break; case IpcMessageErrorType::REQUEST_PUBLISHER_NO_WRITABLE_SHM_SEGMENT: LogWarn() << "Service '" << service.operator cxx::Serialization().toString() - << "' could not be created. RouDi did not find a writable shared memory segment for the current " + << "' could not be created. RouDi did not find a writable shared memory segment for the current " "user. Try using another user or adapt RouDi's config."; errorHandler(Error::kPOSH__RUNTIME_NO_WRITABLE_SHM_SEGMENT, nullptr, iox::ErrorLevel::SEVERE); break; diff --git a/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp b/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp index 9c73effc03c..e7d4492600b 100644 --- a/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp +++ b/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp @@ -250,7 +250,7 @@ class Mepoo_IntegrationTest : public Test { auto currentUser = iox::posix::PosixUser::getUserOfCurrentProcess(); auto memoryManager = m_roudiEnv->m_roudiApp->m_mempoolIntrospection.m_segmentManager - ->getSegmentInformationForUser(currentUser.getName()) + ->getSegmentInformationWithWriteAccessForUser(currentUser.getName()) .m_memoryManager; ASSERT_TRUE(memoryManager.has_value()); m_roudiEnv->m_roudiApp->m_mempoolIntrospection.copyMemPoolInfo(*memoryManager.value(), mempoolInfo); diff --git a/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp b/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp index cb244d8439f..62ffd41ed04 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp @@ -121,7 +121,7 @@ TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getSegmentMappingsEmpt TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getMemoryManagerForUserWithWriteUser)) { - auto memoryManager = sut.getSegmentInformationForUser({"iox_roudi_test2"}).m_memoryManager; + auto memoryManager = sut.getSegmentInformationWithWriteAccessForUser({"iox_roudi_test2"}).m_memoryManager; ASSERT_TRUE(memoryManager.has_value()); ASSERT_THAT(memoryManager.value()->getNumberOfMemPools(), Eq(2u)); @@ -133,10 +133,10 @@ TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getMemoryManagerForUse TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getMemoryManagerForUserFailWithReadOnlyUser)) { - EXPECT_THAT(sut.getSegmentInformationForUser({"iox_roudi_test1"}).m_memoryManager, Eq(nullptr)); + EXPECT_THAT(sut.getSegmentInformationWithWriteAccessForUser({"iox_roudi_test1"}).m_memoryManager, Eq(nullptr)); } TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getMemoryManagerForUserFailWithNonExistingUser)) { - EXPECT_THAT(sut.getSegmentInformationForUser({"no_user"}).m_memoryManager, Eq(nullptr)); + EXPECT_THAT(sut.getSegmentInformationWithWriteAccessForUser({"no_user"}).m_memoryManager, Eq(nullptr)); } diff --git a/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp b/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp index b538e8fa199..d3834204bad 100644 --- a/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp +++ b/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp @@ -83,7 +83,7 @@ class PortManager_test : public Test m_portManager = new PortManagerTester(m_roudiMemoryManager); auto user = iox::posix::PosixUser::getUserOfCurrentProcess().getName(); - auto segmentInfo = m_roudiMemoryManager->segmentManager().value()->getSegmentInformationForUser(user); + auto segmentInfo = m_roudiMemoryManager->segmentManager().value()->getSegmentInformationWithWriteAccessForUser(user); ASSERT_TRUE(segmentInfo.m_memoryManager.has_value()); m_payloadDataSegmentMemoryManager = segmentInfo.m_memoryManager.value(); From 943ff6e4c303fbdfc917c82b0b2ff1527a3e1165 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Fri, 9 Apr 2021 15:15:31 +0200 Subject: [PATCH 054/127] iox-#482 Add copyright header to shell script Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/run_icecubetray.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/iceoryx_examples/icecubetray/run_icecubetray.sh b/iceoryx_examples/icecubetray/run_icecubetray.sh index 76858bcbdd0..7e01a27582e 100644 --- a/iceoryx_examples/icecubetray/run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/run_icecubetray.sh @@ -1,5 +1,21 @@ #!/bin/bash +# Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + WORKSPACE=$(git rev-parse --show-toplevel) # Create groups From 20f77c8f1719a543bfbc445e323ee26cfc054de4 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Fri, 9 Apr 2021 15:39:32 +0200 Subject: [PATCH 055/127] iox-#482 Extend shell script to be callable for integration tests Signed-off-by: Simon Hoinkis --- .../icecubetray/config_and_run_icecubetray.sh | 77 +++++++++++++++++++ .../icecubetray/run_icecubetray.sh | 56 -------------- 2 files changed, 77 insertions(+), 56 deletions(-) create mode 100755 iceoryx_examples/icecubetray/config_and_run_icecubetray.sh delete mode 100644 iceoryx_examples/icecubetray/run_icecubetray.sh diff --git a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh new file mode 100755 index 00000000000..074f82531e0 --- /dev/null +++ b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +WORKSPACE=$(git rev-parse --show-toplevel) +CONFIG=${1:-config} +RUN=${2:-run} +SESSION=iceensemble +tmux="tmux -2 -q" + +if [ "$CONFIG" == "config" ] ; then + # Create groups + sudo groupadd -f privileged + sudo groupadd -f unprivileged + sudo groupadd -f iceoryx + + # Create users w/o homedir + sudo useradd -M perception + sudo useradd -M infotainment + sudo useradd -M roudi + sudo useradd -M notallowed + + # Assign users to group and disable login + sudo usermod -L perception -a -G privileged + sudo usermod -L infotainment -a -G unprivileged + sudo usermod -L perception -a -G iceoryx + sudo usermod -L infotainment -a -G iceoryx + sudo usermod -L roudi -a -G iceoryx + sudo usermod -L notallowed -a -G iceoryx +fi +# If you're using e.g Yocto or QNX refer to the manual on how to set up groups, users and permissions + + +if [ "$RUN" == "run" ] ; then + # Allow RouDi to send SIGKILL to other apps + sudo setcap cap_kill=ep ./build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments + + $tmux kill-server + + $tmux has-session -t $SESSION + if [ $? -eq 0 ]; then + echo "Session $SESSION already exists. Attaching to session." + $tmux attach -t $SESSION + exit 0; + fi + + command -v tmux >/dev/null 2>&1 || { echo >&2 "tmux is not installed but required. Trying to install it..."; sudo apt-get install tmux; } + + $tmux new-session -d -s $SESSION + # Start custom RouDi in 'iceoryx' group + $tmux new-window -a -t $SESSION 'sudo -u roudi -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments' + + # Start perception app as 'perception' user + $tmux split-window -t 0 -h 'sudo -u perception -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-radar' + + # Start display app as 'infotainment' user + $tmux split-window -t 1 -h 'sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-display' + + # Start cheeky app as 'notallowed' user + $tmux split-window -t 0 -h 'sudo -u notallowed -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-cheeky' + + $tmux attach -t $SESSION +fi diff --git a/iceoryx_examples/icecubetray/run_icecubetray.sh b/iceoryx_examples/icecubetray/run_icecubetray.sh deleted file mode 100644 index 7e01a27582e..00000000000 --- a/iceoryx_examples/icecubetray/run_icecubetray.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2021 by Apex.AI Inc. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 - -WORKSPACE=$(git rev-parse --show-toplevel) - -# Create groups -sudo groupadd -f privileged -sudo groupadd -f unprivileged -sudo groupadd -f iceoryx - -# Create users w/o homedir -sudo useradd -M perception -sudo useradd -M infotainment -sudo useradd -M roudi -sudo useradd -M notallowed - -# Assign users to group and disable login -sudo usermod -L perception -a -G privileged -sudo usermod -L infotainment -a -G unprivileged -sudo usermod -L perception -a -G iceoryx -sudo usermod -L infotainment -a -G iceoryx -sudo usermod -L roudi -a -G iceoryx -sudo usermod -L notallowed -a -G iceoryx - -# If you're using e.g Yocto or QNX refer to the manual on how to set up groups, users and permissions - - -# Allow RouDi to send SIGKILL to other apps -sudo setcap cap_kill=ep ./build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments - -# Start custom RouDi in 'iceoryx' group -sudo -u roudi -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments - -# Start perception app as 'perception' user -sudo -u perception -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-radar - -# Start display app as 'infotainment' user -sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-display - -# Start cheeky app as 'notallowed' user -sudo -u notallowed -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-cheeky From 9dd49d92577181cde51022c2a4bd9046c1bf38cc Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Fri, 9 Apr 2021 16:27:29 +0200 Subject: [PATCH 056/127] iox-#482 Add integration test for icecubetray Signed-off-by: Simon Hoinkis --- .../icecubetray/config_and_run_icecubetray.sh | 32 +++++-- .../icecubetray/iox_cheeky_app.cpp | 2 +- .../test_icecubetray_example.py | 94 +++++++++++++++++++ 3 files changed, 120 insertions(+), 8 deletions(-) create mode 100755 iceoryx_integrationtest/iceoryx_integrationtest/test_icecubetray_example.py diff --git a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh index 074f82531e0..3325d206038 100755 --- a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh @@ -17,12 +17,29 @@ # SPDX-License-Identifier: Apache-2.0 WORKSPACE=$(git rev-parse --show-toplevel) -CONFIG=${1:-config} -RUN=${2:-run} -SESSION=iceensemble +CONFIG="OFF" +RUN="OFF" +SESSION=icecubetray tmux="tmux -2 -q" -if [ "$CONFIG" == "config" ] ; then +while (( "$#" )); do + case "$1" in + config) + CONFIG="ON" + shift 1 + ;; + run) + RUN="ON" + shift 1 + ;; + *) + echo "Invalid argument '$1'" + exit -1 + ;; + esac +done + +if [ "$CONFIG" == "ON" ] ; then # Create groups sudo groupadd -f privileged sudo groupadd -f unprivileged @@ -45,7 +62,7 @@ fi # If you're using e.g Yocto or QNX refer to the manual on how to set up groups, users and permissions -if [ "$RUN" == "run" ] ; then +if [ "$RUN" == "ON" ] ; then # Allow RouDi to send SIGKILL to other apps sudo setcap cap_kill=ep ./build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments @@ -61,6 +78,7 @@ if [ "$RUN" == "run" ] ; then command -v tmux >/dev/null 2>&1 || { echo >&2 "tmux is not installed but required. Trying to install it..."; sudo apt-get install tmux; } $tmux new-session -d -s $SESSION + # Start custom RouDi in 'iceoryx' group $tmux new-window -a -t $SESSION 'sudo -u roudi -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments' @@ -68,10 +86,10 @@ if [ "$RUN" == "run" ] ; then $tmux split-window -t 0 -h 'sudo -u perception -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-radar' # Start display app as 'infotainment' user - $tmux split-window -t 1 -h 'sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-display' + $tmux split-window -t 1 -v 'sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-display' # Start cheeky app as 'notallowed' user - $tmux split-window -t 0 -h 'sudo -u notallowed -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-cheeky' + $tmux split-window -t 0 -v 'sudo -u notallowed -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-cheeky' $tmux attach -t $SESSION fi diff --git a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp index 9f9184986cb..3b76db5732d 100644 --- a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp +++ b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp @@ -41,7 +41,7 @@ int main() // initialize runtime iox::runtime::PoshRuntime::initRuntime(APP_NAME); - // Subscribers can be create without any readable shared memory segment, in this case no data will ever arrive + // Subscribers can be created without any readable shared memory segment, in this case no data will ever arrive iox::popo::Subscriber subscriber({"Radar", "FrontLeft", "Object"}); // When starting this app with the user 'notallowed', the publisher object can't be initalised correctly because diff --git a/iceoryx_integrationtest/iceoryx_integrationtest/test_icecubetray_example.py b/iceoryx_integrationtest/iceoryx_integrationtest/test_icecubetray_example.py new file mode 100755 index 00000000000..c850365b164 --- /dev/null +++ b/iceoryx_integrationtest/iceoryx_integrationtest/test_icecubetray_example.py @@ -0,0 +1,94 @@ +# Copyright (c) 2021 by Apex.AI Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import unittest +import launch +from launch_ros.substitutions import ExecutableInPackage +import launch_testing +import launch_testing.actions +from launch_testing.asserts import assertSequentialStdout + +import pytest + +# @brief Test goal: "Integrationtest for the icecubetray example of iceoryx" +# @pre setup ROS2 launch executable for RouDi (debug mode) the example processes +# @post check if all applications return exitcode 0 (success) after test run +@pytest.mark.launch_test +def generate_test_description(): + + proc_env = os.environ.copy() + colcon_prefix_path = os.environ.get('COLCON_PREFIX_PATH', '') + + # Configure users and groups necessary to run this integration test + subprocess.call(['sh', '$(git rev-parse --show-toplevel)/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh config']) + + executable_list = ['iox-cpp-display', 'iox-cpp-radar', 'iox-cpp-cheeky'] + user_list = ['infotainment', 'perception', 'notallowed'] + + for exec, user in zip(executable_list, user_list): + tmp_exec = os.path.join( + colcon_prefix_path, + 'example_waitset/bin/', + exec) + tmp_process = launch.actions.ExecuteProcess( + cmd=['sudo -u ', user, ' -g iceoryx --', tmp_exec], + env=proc_env, output='screen') + process_list.append(tmp_process) + + print("Process list:", process_list) + + roudi_executable = os.path.join( + colcon_prefix_path, + 'iceoryx_icecubetray/bin/', + 'iox-cpp-roudi-static-segments' + ) + roudi_process = launch.actions.ExecuteProcess( + cmd=[roudi_executable, '-l', 'debug'], + env=proc_env, output='screen', + sigterm_timeout='20') + + return launch.LaunchDescription([ + roudi_process, + process_list[0], + process_list[1], + process_list[2], + launch_testing.actions.ReadyToTest() + ]), {'iox-cpp-radar': process_list[0], 'iox-cpp-display': process_list[1], 'iox-cpp-cheeky': process_list[2]} + + +class TestIcecubetrayExample(unittest.TestCase): + def test_roudi_ready(self, proc_output): + proc_output.assertWaitFor( + 'RouDi is ready for clients', timeout=45, stream='stdout') + + def test_icecubetray_radar(self, proc_output): + proc_output.assertWaitFor( + 'iox-cpp-radar sent value: 10', timeout=45, stream='stdout') + + def test_icecubetray_display(self, proc_output): + proc_output.assertWaitFor( + 'iox-cpp-display sending value: 10', timeout=45, stream='stdout') + + def test_icecubetray_cheeky(self, proc_output): + proc_output.assertWaitFor( + 'RouDi did not find a writable shared memory segment for the current user.', timeout=45, stream='stdout') + + +@ launch_testing.post_shutdown_test() +class TestIcecubetraySetExampleExitCodes(unittest.TestCase): + def test_exit_code(self, proc_info): + launch_testing.asserts.assertExitCodes(proc_info) From f2afbbb852d9d1a1a8fcd53b66d662d5c564c608 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Fri, 9 Apr 2021 17:08:55 +0200 Subject: [PATCH 057/127] iox-#482 Fix typos, remove un-used code and other minor cleanups Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/CMakeLists.txt | 4 +- iceoryx_examples/icecubetray/README.md | 22 ++++++----- .../icecubetray/config_and_run_icecubetray.sh | 2 +- .../icecubetray/iox_cheeky_app.cpp | 14 ------- .../roudi_main_static_segements.cpp | 4 +- .../internal/mepoo/segment_manager.hpp | 14 +++---- .../internal/mepoo/segment_manager.inl | 37 +++++++++---------- .../internal/roudi/process_manager.hpp | 2 +- iceoryx_posh/source/roudi/process_manager.cpp | 2 +- .../test_roudi_process_manager.cpp | 6 ++- 10 files changed, 47 insertions(+), 60 deletions(-) diff --git a/iceoryx_examples/icecubetray/CMakeLists.txt b/iceoryx_examples/icecubetray/CMakeLists.txt index 53646962fec..a3c65eeb9e5 100644 --- a/iceoryx_examples/icecubetray/CMakeLists.txt +++ b/iceoryx_examples/icecubetray/CMakeLists.txt @@ -60,7 +60,7 @@ target_link_libraries(iox-cpp-roudi-static-segments target_compile_options(iceperf-roudi PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) -set_target_properties(iox-cpp-radar iox-cpp-display iox-cpp-roudi-static-segments PROPERTIES +set_target_properties(iox-cpp-radar iox-cpp-display iox-cpp-cheeky iox-cpp-roudi-static-segments PROPERTIES CXX_STANDARD_REQUIRED ON CXX_STANDARD ${ICEORYX_CXX_STANDARD} POSITION_INDEPENDENT_CODE ON @@ -68,6 +68,6 @@ set_target_properties(iox-cpp-radar iox-cpp-display iox-cpp-roudi-static-segment install( - TARGETS iox-cpp-radar iox-cpp-display iox-cpp-roudi-static-segments + TARGETS iox-cpp-radar iox-cpp-display iox-cpp-cheeky iox-cpp-roudi-static-segments RUNTIME DESTINATION bin ) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/icecubetray/README.md index f26e8a0f73f..94dc28daf3c 100644 --- a/iceoryx_examples/icecubetray/README.md +++ b/iceoryx_examples/icecubetray/README.md @@ -6,7 +6,7 @@ This example demonstrates how access rights can be applied to shared memory segm It provides a custom RouDi, a radar and a display application. !!! hint - The access rights feature is only supported on Linux-based operating systems + The access right feature is only supported on Linux-based operating systems ## Expected output @@ -60,14 +60,14 @@ other POSIX operating system. RouDi needs to be able to send a _SIGKILL_ signal +-----------------------+ ``` -#### RouDi and Apps +### RouDi and Apps -##### Working setup +#### Working setup RouDi is built with two static shared memory segments _infotainment_ and _privileged_. The access rights of the segments are configured as depicted in the graphic above. The `roudiConfig` is composed of a memory pool config called `mepooConfig`. When the segement is created, one needs to -specific the reader (first string), write group (second string) as well as the `mepooConfig` (last parameter). +specific the reader group (first string), writer group (second string) as well as the `mepooConfig` (last parameter). ```cpp iox::RouDiConfig_t roudiConfig; @@ -89,7 +89,7 @@ The display app is started with the user _infotainment_. It reads the topic `{"R !!! hint It's advised to create per writer group only one shared memory segement (e.g. not two segements with `w: infotainment`). - In this case it wouldn't be possible to control which segment will be used. (=> add a test for that) + In this case it wouldn't be possible to control which segment will be used. The shared memory segments can be found under `/dev/shm` @@ -106,18 +106,22 @@ drwxr-xr-x 6 root root 460 Apr 6 15:53 .. !!! note Note the shared memory managment segment is always available for everyone to **read** and **write** -##### Not-working setup +#### Not-working setup -The cheeky app is started with the user __notallowed_. It has neither write nor read access to any shared memory segment. Hence, RouDi will print a warning in this case. +The cheeky app is started with the user _notallowed_. It has neither write nor read access to any shared memory segment. Hence, RouDi will print a warning in this case. -Despite having no read access, subscriber can still be created. In this case no data will ever arrive. +Despite having no read access, subscribers can still be created. In this case no data will ever arrive. ```cpp iox::popo::Subscriber subscriber({"Radar", "FrontLeft", "Object"}); ``` -When creating a publisher, no valid corresponding shared memory object can be created by RouDi. Hence, an error will be printed and the cheeky app will stop. +When creating and requesting a publisher RouDi will answer with an error, as there is no write access. Hence, an error will be printed and the cheeky app will stop. ```cpp iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); ``` + +
+[Check out icecubetray on GitHub :fontawesome-brands-github:](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icecubetray){ .md-button } +
diff --git a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh index 3325d206038..0f81319978b 100755 --- a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh @@ -59,8 +59,8 @@ if [ "$CONFIG" == "ON" ] ; then sudo usermod -L roudi -a -G iceoryx sudo usermod -L notallowed -a -G iceoryx fi -# If you're using e.g Yocto or QNX refer to the manual on how to set up groups, users and permissions +# If you're using e.g Yocto or QNX refer to the manual on how to set up groups, users and permissions if [ "$RUN" == "ON" ] ; then # Allow RouDi to send SIGKILL to other apps diff --git a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp index 3b76db5732d..0d9f25ff07e 100644 --- a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp +++ b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp @@ -19,25 +19,11 @@ #include "iceoryx_posh/popo/publisher.hpp" #include "iceoryx_posh/popo/subscriber.hpp" #include "iceoryx_posh/runtime/posh_runtime.hpp" -#include "iceoryx_utils/posix_wrapper/signal_handler.hpp" -#include - -bool killswitch = false; constexpr char APP_NAME[] = "iox-cpp-cheeky"; -static void sigHandler(int f_sig [[gnu::unused]]) -{ - // caught SIGINT or SIGTERM, now exit gracefully - killswitch = true; -} - int main() { - // register sigHandler - auto signalIntGuard = iox::posix::registerSignalHandler(iox::posix::Signal::INT, sigHandler); - auto signalTermGuard = iox::posix::registerSignalHandler(iox::posix::Signal::TERM, sigHandler); - // initialize runtime iox::runtime::PoshRuntime::initRuntime(APP_NAME); diff --git a/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp b/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp index aef0ead8b8c..daf4ca82a38 100644 --- a/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp +++ b/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp @@ -23,8 +23,6 @@ int main(int argc, char* argv[]) { using iox::roudi::IceOryxRouDiApp; - static constexpr uint32_t ONE_KILOBYTE = 1024U; - static constexpr uint32_t ONE_MEGABYTE = 1024U * 1024; iox::config::CmdLineParser cmdLineParser; auto cmdLineArgs = cmdLineParser.parse(argc, argv); @@ -42,7 +40,7 @@ int main(int argc, char* argv[]) // We only send very small data, just one mempool per segment mepooConfig.addMemPool({128, 1000}); - /// Create an Entry for a new Shared Memory Segment from the MempoolConfig and add it to the RouDiConfig + /// Create an entry for a new shared memory segment from the mempooConfig and add it to the roudiConfig roudiConfig.m_sharedMemorySegments.push_back({"unprivileged", "privileged", mepooConfig}); roudiConfig.m_sharedMemorySegments.push_back({"infotainment", "infotainment", mepooConfig}); diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp index a05d21c4247..f95040c1494 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp @@ -41,7 +41,7 @@ template > class SegmentManager { public: - SegmentManager(const SegmentConfig& f_segmentConfig, posix::Allocator* f_managementAllocator) noexcept; + SegmentManager(const SegmentConfig& segmentConfig, posix::Allocator* managementAllocator) noexcept; ~SegmentManager() noexcept = default; SegmentManager(const SegmentManager& rhs) = delete; @@ -85,15 +85,15 @@ class SegmentManager using SegmentMappingContainer = cxx::vector; - SegmentMappingContainer getSegmentMappings(posix::PosixUser f_user) noexcept; - SegmentUserInformation getSegmentInformationWithWriteAccessForUser(posix::PosixUser f_user) noexcept; + SegmentMappingContainer getSegmentMappings(posix::PosixUser user) noexcept; + SegmentUserInformation getSegmentInformationWithWriteAccessForUser(posix::PosixUser user) noexcept; - static uint64_t requiredManagementMemorySize(const SegmentConfig& f_config) noexcept; - static uint64_t requiredChunkMemorySize(const SegmentConfig& f_config) noexcept; - static uint64_t requiredFullMemorySize(const SegmentConfig& f_config) noexcept; + static uint64_t requiredManagementMemorySize(const SegmentConfig& config) noexcept; + static uint64_t requiredChunkMemorySize(const SegmentConfig& config) noexcept; + static uint64_t requiredFullMemorySize(const SegmentConfig& config) noexcept; private: - bool createSegment(const SegmentConfig::SegmentEntry& f_segmentEntry) noexcept; + bool createSegment(const SegmentConfig::SegmentEntry& segmentEntry) noexcept; private: template diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl index f6437fdc41f..c5059a66ccd 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl @@ -27,28 +27,25 @@ namespace iox namespace mepoo { template -inline SegmentManager::SegmentManager(const SegmentConfig& f_segmentConfig, - posix::Allocator* f_managementAllocator) noexcept - : m_managementAllocator(f_managementAllocator) +inline SegmentManager::SegmentManager(const SegmentConfig& segmentConfig, + posix::Allocator* managementAllocator) noexcept + : m_managementAllocator(managementAllocator) { - for (const auto& segmentEntry : f_segmentConfig.m_sharedMemorySegments) + for (const auto& segmentEntry : segmentConfig.m_sharedMemorySegments) { createSegment(segmentEntry); } } template -inline bool SegmentManager::createSegment(const SegmentConfig::SegmentEntry& f_segmentEntry) noexcept +inline bool SegmentManager::createSegment(const SegmentConfig::SegmentEntry& segmentEntry) noexcept { if (m_segmentContainer.size() < m_segmentContainer.capacity()) { - auto readerGroup = iox::posix::PosixGroup(f_segmentEntry.m_readerGroup); - auto writerGroup = iox::posix::PosixGroup(f_segmentEntry.m_writerGroup); - m_segmentContainer.emplace_back(f_segmentEntry.m_mempoolConfig, - *m_managementAllocator, - readerGroup, - writerGroup, - f_segmentEntry.m_memoryInfo); + auto readerGroup = iox::posix::PosixGroup(segmentEntry.m_readerGroup); + auto writerGroup = iox::posix::PosixGroup(segmentEntry.m_writerGroup); + m_segmentContainer.emplace_back( + segmentEntry.m_mempoolConfig, *m_managementAllocator, readerGroup, writerGroup, segmentEntry.m_memoryInfo); return true; } else @@ -60,10 +57,10 @@ inline bool SegmentManager::createSegment(const SegmentConfig::Segm template inline typename SegmentManager::SegmentMappingContainer -SegmentManager::getSegmentMappings(posix::PosixUser f_user) noexcept +SegmentManager::getSegmentMappings(posix::PosixUser user) noexcept { // get all the groups the user is in - auto l_groupContainer = f_user.getGroups(); + auto l_groupContainer = user.getGroups(); SegmentManager::SegmentMappingContainer l_mappingContainer; bool l_foundInWriterGroup = false; @@ -145,10 +142,10 @@ SegmentManager::getSegmentInformationWithWriteAccessForUser(posix:: } template -uint64_t SegmentManager::requiredManagementMemorySize(const SegmentConfig& f_config) noexcept +uint64_t SegmentManager::requiredManagementMemorySize(const SegmentConfig& config) noexcept { uint64_t memorySize{0u}; - for (auto segment : f_config.m_sharedMemorySegments) + for (auto segment : config.m_sharedMemorySegments) { memorySize += MemoryManager::requiredManagementMemorySize(segment.m_mempoolConfig); } @@ -156,10 +153,10 @@ uint64_t SegmentManager::requiredManagementMemorySize(const Segment } template -uint64_t SegmentManager::requiredChunkMemorySize(const SegmentConfig& f_config) noexcept +uint64_t SegmentManager::requiredChunkMemorySize(const SegmentConfig& config) noexcept { uint64_t memorySize{0u}; - for (auto segment : f_config.m_sharedMemorySegments) + for (auto segment : config.m_sharedMemorySegments) { memorySize += MemoryManager::requiredChunkMemorySize(segment.m_mempoolConfig); } @@ -167,9 +164,9 @@ uint64_t SegmentManager::requiredChunkMemorySize(const SegmentConfi } template -uint64_t SegmentManager::requiredFullMemorySize(const SegmentConfig& f_config) noexcept +uint64_t SegmentManager::requiredFullMemorySize(const SegmentConfig& config) noexcept { - return requiredManagementMemorySize(f_config) + requiredChunkMemorySize(f_config); + return requiredManagementMemorySize(config) + requiredChunkMemorySize(config); } } // namespace mepoo diff --git a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp index dce99c6137d..bc8df5b9d2d 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp @@ -150,7 +150,7 @@ class ProcessManager : public ProcessManagerInterface /// @param [in] name of the process; this is equal to the IPC channel name, which is used for communication /// @param [in] pid is the host system process id - /// @param [in] payloadMemoryManager is a reference to the payload memory manager for this process + /// @param [in] user is user used in the operating system for this process /// @param [in] isMonitored indicates if the process should be monitored for being alive /// @param [in] transmissionTimestamp is an ID for the application to check for the expected response /// @param [in] sessionId is an ID generated by RouDi to prevent sending outdated IPC channel transmission diff --git a/iceoryx_posh/source/roudi/process_manager.cpp b/iceoryx_posh/source/roudi/process_manager.cpp index 70fc0fad675..2c201bda114 100644 --- a/iceoryx_posh/source/roudi/process_manager.cpp +++ b/iceoryx_posh/source/roudi/process_manager.cpp @@ -531,7 +531,7 @@ void ProcessManager::addPublisherForProcess(const RuntimeName_t& name, if (!segmentInfo.m_memoryManager.has_value()) { - // Tell the app not writable shared memory segment was found + // Tell the app no writable shared memory segment was found runtime::IpcMessage sendBuffer; sendBuffer << runtime::IpcMessageTypeToString(runtime::IpcMessageType::ERROR); sendBuffer << runtime::IpcMessageErrorTypeToString( diff --git a/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp b/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp index ad4b0108e5c..011d34866a3 100644 --- a/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp +++ b/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp @@ -121,8 +121,10 @@ TEST_F(ProcessManager_test, HandleProcessShutdownPreparationRequestWorks) m_sut->registerProcess(m_processname, m_pid, m_user, m_isMonitored, 1U, 1U, m_versionInfo); auto user = iox::posix::PosixUser::getUserOfCurrentProcess().getName(); - auto payloadDataSegmentMemoryManager = - m_roudiMemoryManager->segmentManager().value()->getSegmentInformationForUser(user).m_memoryManager; + auto payloadDataSegmentMemoryManager = m_roudiMemoryManager->segmentManager() + .value() + ->getSegmentInformationWithWriteAccessForUser(user) + .m_memoryManager; ASSERT_TRUE(payloadDataSegmentMemoryManager.has_value()); From 25577872a6fa63425d43889510b015f4e38ced8b Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Fri, 9 Apr 2021 17:10:26 +0200 Subject: [PATCH 058/127] iox-#482 Move cap_kill assignment to config stage Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/config_and_run_icecubetray.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh index 0f81319978b..96a72e6fb8e 100755 --- a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh @@ -58,14 +58,14 @@ if [ "$CONFIG" == "ON" ] ; then sudo usermod -L infotainment -a -G iceoryx sudo usermod -L roudi -a -G iceoryx sudo usermod -L notallowed -a -G iceoryx + + # Allow RouDi to send SIGKILL to other apps + sudo setcap cap_kill=ep $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments fi # If you're using e.g Yocto or QNX refer to the manual on how to set up groups, users and permissions if [ "$RUN" == "ON" ] ; then - # Allow RouDi to send SIGKILL to other apps - sudo setcap cap_kill=ep ./build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments - $tmux kill-server $tmux has-session -t $SESSION From c6672ae6833963e06499b5282f2adc08ec76c6c7 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Fri, 9 Apr 2021 18:06:52 +0200 Subject: [PATCH 059/127] iox-#482 Try to fix windows build Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/CMakeLists.txt | 2 +- .../platform/win/include/iceoryx_utils/platform/stat.hpp | 3 +++ .../platform/win/include/iceoryx_utils/platform/types.hpp | 8 ++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/iceoryx_examples/icecubetray/CMakeLists.txt b/iceoryx_examples/icecubetray/CMakeLists.txt index a3c65eeb9e5..1c734c28459 100644 --- a/iceoryx_examples/icecubetray/CMakeLists.txt +++ b/iceoryx_examples/icecubetray/CMakeLists.txt @@ -58,7 +58,7 @@ target_link_libraries(iox-cpp-roudi-static-segments iceoryx_posh::iceoryx_posh_roudi ) -target_compile_options(iceperf-roudi PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) +target_compile_options(iox-cpp-roudi-static-segments PRIVATE ${ICEORYX_WARNINGS} ${ICEORYX_SANITIZER_FLAGS}) set_target_properties(iox-cpp-radar iox-cpp-display iox-cpp-cheeky iox-cpp-roudi-static-segments PROPERTIES CXX_STANDARD_REQUIRED ON diff --git a/iceoryx_utils/platform/win/include/iceoryx_utils/platform/stat.hpp b/iceoryx_utils/platform/win/include/iceoryx_utils/platform/stat.hpp index 929d85b6c77..b3632157fc5 100644 --- a/iceoryx_utils/platform/win/include/iceoryx_utils/platform/stat.hpp +++ b/iceoryx_utils/platform/win/include/iceoryx_utils/platform/stat.hpp @@ -26,5 +26,8 @@ #define S_IROTH 4 #define S_IWOTH 5 #define S_IRWXU 6 +#define S_IXUSR 7 +#define S_IXGRP 8 +#define S_IRWXO 9 #endif // IOX_UTILS_WIN_PLATFORM_STAT_HPP diff --git a/iceoryx_utils/platform/win/include/iceoryx_utils/platform/types.hpp b/iceoryx_utils/platform/win/include/iceoryx_utils/platform/types.hpp index 13e56084314..3b1817faa32 100644 --- a/iceoryx_utils/platform/win/include/iceoryx_utils/platform/types.hpp +++ b/iceoryx_utils/platform/win/include/iceoryx_utils/platform/types.hpp @@ -28,9 +28,9 @@ using blksize_t = int; using blkcnt_t = int; // using off_t = int; -// mode_t umask(mode_t mask) -//{ -// return mode_t(); -//} +mode_t umask(mode_t mask) +{ + return 0; +} #endif // IOX_UTILS_WIN_PLATFORM_TYPES_HPP From 2b3dcbf5108eb3590400729c1011a64a8f93873e Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Sat, 10 Apr 2021 16:26:32 +0200 Subject: [PATCH 060/127] iox-#482 Fix segment manager tests and revert windows change Signed-off-by: Simon Hoinkis --- .../test/moduletests/test_mepoo_segment_management.cpp | 4 ++-- .../platform/win/include/iceoryx_utils/platform/types.hpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp b/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp index 62ffd41ed04..89164436e2f 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp @@ -133,10 +133,10 @@ TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getMemoryManagerForUse TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getMemoryManagerForUserFailWithReadOnlyUser)) { - EXPECT_THAT(sut.getSegmentInformationWithWriteAccessForUser({"iox_roudi_test1"}).m_memoryManager, Eq(nullptr)); + EXPECT_FALSE(sut.getSegmentInformationWithWriteAccessForUser({"iox_roudi_test1"}).m_memoryManager.has_value()); } TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getMemoryManagerForUserFailWithNonExistingUser)) { - EXPECT_THAT(sut.getSegmentInformationWithWriteAccessForUser({"no_user"}).m_memoryManager, Eq(nullptr)); + EXPECT_FALSE(sut.getSegmentInformationWithWriteAccessForUser({"no_user"}).m_memoryManager.has_value()); } diff --git a/iceoryx_utils/platform/win/include/iceoryx_utils/platform/types.hpp b/iceoryx_utils/platform/win/include/iceoryx_utils/platform/types.hpp index 3b1817faa32..13e56084314 100644 --- a/iceoryx_utils/platform/win/include/iceoryx_utils/platform/types.hpp +++ b/iceoryx_utils/platform/win/include/iceoryx_utils/platform/types.hpp @@ -28,9 +28,9 @@ using blksize_t = int; using blkcnt_t = int; // using off_t = int; -mode_t umask(mode_t mask) -{ - return 0; -} +// mode_t umask(mode_t mask) +//{ +// return mode_t(); +//} #endif // IOX_UTILS_WIN_PLATFORM_TYPES_HPP From dafb3d5352308ab35440757f4f9e34d40ea351b5 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Sat, 10 Apr 2021 22:30:14 +0200 Subject: [PATCH 061/127] iox-#482 Add two test cases for SegmentManager and refactor untestable code Signed-off-by: Simon Hoinkis --- .../internal/mepoo/segment_manager.hpp | 2 +- .../internal/mepoo/segment_manager.inl | 65 ++++++++----------- .../test_mepoo_segment_management.cpp | 60 ++++++++++++++++- .../error_handling/error_handling.hpp | 1 - 4 files changed, 86 insertions(+), 42 deletions(-) diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp index f95040c1494..78f1214d4df 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp @@ -93,7 +93,7 @@ class SegmentManager static uint64_t requiredFullMemorySize(const SegmentConfig& config) noexcept; private: - bool createSegment(const SegmentConfig::SegmentEntry& segmentEntry) noexcept; + void createSegment(const SegmentConfig::SegmentEntry& segmentEntry) noexcept; private: template diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl index c5059a66ccd..aa6ef61c07f 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl @@ -31,6 +31,7 @@ inline SegmentManager::SegmentManager(const SegmentConfig& segmentC posix::Allocator* managementAllocator) noexcept : m_managementAllocator(managementAllocator) { + cxx::Expects(segmentConfig.m_sharedMemorySegments.capacity() <= m_segmentContainer.capacity()); for (const auto& segmentEntry : segmentConfig.m_sharedMemorySegments) { createSegment(segmentEntry); @@ -38,21 +39,12 @@ inline SegmentManager::SegmentManager(const SegmentConfig& segmentC } template -inline bool SegmentManager::createSegment(const SegmentConfig::SegmentEntry& segmentEntry) noexcept +inline void SegmentManager::createSegment(const SegmentConfig::SegmentEntry& segmentEntry) noexcept { - if (m_segmentContainer.size() < m_segmentContainer.capacity()) - { - auto readerGroup = iox::posix::PosixGroup(segmentEntry.m_readerGroup); - auto writerGroup = iox::posix::PosixGroup(segmentEntry.m_writerGroup); - m_segmentContainer.emplace_back( - segmentEntry.m_mempoolConfig, *m_managementAllocator, readerGroup, writerGroup, segmentEntry.m_memoryInfo); - return true; - } - else - { - errorHandler(Error::kMEPOO__SEGMENT_CONTAINER_OVERFLOW); - return false; - } + auto readerGroup = iox::posix::PosixGroup(segmentEntry.m_readerGroup); + auto writerGroup = iox::posix::PosixGroup(segmentEntry.m_writerGroup); + m_segmentContainer.emplace_back( + segmentEntry.m_mempoolConfig, *m_managementAllocator, readerGroup, writerGroup, segmentEntry.m_memoryInfo); } template @@ -60,13 +52,13 @@ inline typename SegmentManager::SegmentMappingContainer SegmentManager::getSegmentMappings(posix::PosixUser user) noexcept { // get all the groups the user is in - auto l_groupContainer = user.getGroups(); + auto groupContainer = user.getGroups(); - SegmentManager::SegmentMappingContainer l_mappingContainer; - bool l_foundInWriterGroup = false; + SegmentManager::SegmentMappingContainer mappingContainer; + bool foundInWriterGroup = false; // with the groups we can get all the segments (read or write) for the user - for (const auto& groupID : l_groupContainer) + for (const auto& groupID : groupContainer) { for (const auto& segment : m_segmentContainer) { @@ -74,14 +66,14 @@ SegmentManager::getSegmentMappings(posix::PosixUser user) noexcept { // a user is allowed to be only in one writer group, as we currently only support one memory manager per // process - if (!l_foundInWriterGroup) + if (!foundInWriterGroup) { - l_mappingContainer.emplace_back("/" + segment.getWriterGroup().getName(), - segment.getSharedMemoryObject().getBaseAddress(), - segment.getSharedMemoryObject().getSizeInBytes(), - true, - segment.getSegmentId()); - l_foundInWriterGroup = true; + mappingContainer.emplace_back("/" + segment.getWriterGroup().getName(), + segment.getSharedMemoryObject().getBaseAddress(), + segment.getSharedMemoryObject().getSizeInBytes(), + true, + segment.getSegmentId()); + foundInWriterGroup = true; } else { @@ -91,29 +83,26 @@ SegmentManager::getSegmentMappings(posix::PosixUser user) noexcept } } - for (const auto& groupID : l_groupContainer) + for (const auto& groupID : groupContainer) { for (const auto& segment : m_segmentContainer) { // only add segments which are not yet added as writer if (segment.getReaderGroup() == groupID - && std::find_if(l_mappingContainer.begin(), - l_mappingContainer.end(), - [&](const SegmentMapping& mapping) { - return mapping.m_startAddress == segment.getSharedMemoryObject().getBaseAddress(); - }) - == l_mappingContainer.end()) + && std::find_if(mappingContainer.begin(), mappingContainer.end(), [&](const SegmentMapping& mapping) { + return mapping.m_startAddress == segment.getSharedMemoryObject().getBaseAddress(); + }) == mappingContainer.end()) { - l_mappingContainer.emplace_back("/" + segment.getWriterGroup().getName(), - segment.getSharedMemoryObject().getBaseAddress(), - segment.getSharedMemoryObject().getSizeInBytes(), - false, - segment.getSegmentId()); + mappingContainer.emplace_back("/" + segment.getWriterGroup().getName(), + segment.getSharedMemoryObject().getBaseAddress(), + segment.getSharedMemoryObject().getSizeInBytes(), + false, + segment.getSegmentId()); } } } - return l_mappingContainer; + return mappingContainer; } template diff --git a/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp b/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp index 89164436e2f..de3aeeacea1 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp @@ -29,6 +29,19 @@ using namespace ::testing; using namespace iox::mepoo; +using namespace iox::posix; + +class MePooSegmentMock +{ + public: + MePooSegmentMock(const MePooConfig& mempoolConfig [[gnu::unused]], + Allocator& managementAllocator [[gnu::unused]], + const PosixGroup& readerGroup [[gnu::unused]], + const PosixGroup& writerGroup [[gnu::unused]], + const MemoryInfo& memoryInfo [[gnu::unused]]) noexcept + { + } +}; class SegmentManager_test : public Test { @@ -70,8 +83,26 @@ class SegmentManager_test : public Test SegmentConfig getSegmentConfig() { SegmentConfig config; - segmentConfig.m_sharedMemorySegments.push_back({"iox_roudi_test1", "iox_roudi_test2", mepooConfig}); - segmentConfig.m_sharedMemorySegments.push_back({"iox_roudi_test2", "iox_roudi_test3", mepooConfig}); + config.m_sharedMemorySegments.push_back({"iox_roudi_test1", "iox_roudi_test2", mepooConfig}); + config.m_sharedMemorySegments.push_back({"iox_roudi_test2", "iox_roudi_test3", mepooConfig}); + return config; + } + + SegmentConfig getInvalidSegmentConfig() + { + SegmentConfig config; + config.m_sharedMemorySegments.push_back({"iox_roudi_test1", "iox_roudi_test1", mepooConfig}); + config.m_sharedMemorySegments.push_back({"iox_roudi_test3", "iox_roudi_test1", mepooConfig}); + return config; + } + + SegmentConfig getSegmentConfigWithMaximumNumberOfSegements() + { + SegmentConfig config; + for (uint64_t i = 0U; i < iox::MAX_SHM_SEGMENTS; ++i) + { + config.m_sharedMemorySegments.push_back({"iox_roudi_test1", "iox_roudi_test1", mepooConfig}); + } return config; } @@ -140,3 +171,28 @@ TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getMemoryManagerForUse { EXPECT_FALSE(sut.getSegmentInformationWithWriteAccessForUser({"no_user"}).m_memoryManager.has_value()); } + +TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(addingMoreThanOneWriterGroupFails)) +{ + auto errorHandlerCalled{false}; + iox::Error receivedError{iox::Error::kNO_ERROR}; + auto errorHandlerGuard = iox::ErrorHandler::SetTemporaryErrorHandler( + [&errorHandlerCalled, + &receivedError](const iox::Error error, const std::function, const iox::ErrorLevel) { + errorHandlerCalled = true; + receivedError = error; + }); + + SegmentConfig segmentConfig = getInvalidSegmentConfig(); + SegmentManager<> sut{segmentConfig, &allocator}; + sut.getSegmentMappings(PosixUser("iox_roudi_test1")); + + EXPECT_TRUE(errorHandlerCalled); + EXPECT_THAT(receivedError, Eq(iox::Error::kMEPOO__USER_WITH_MORE_THAN_ONE_WRITE_SEGMENT)); +} + +TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(addingMaximumNumberOfSegmentsWorks)) +{ + SegmentConfig segmentConfig = getSegmentConfigWithMaximumNumberOfSegements(); + SegmentManager sut{segmentConfig, &allocator}; +} diff --git a/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp b/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp index 59788edf525..5c6593ca2f3 100644 --- a/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp +++ b/iceoryx_utils/include/iceoryx_utils/error_handling/error_handling.hpp @@ -106,7 +106,6 @@ namespace iox error(MEPOO__MEMPOOL_ADDMEMPOOL_AFTER_GENERATECHUNKMANAGEMENTPOOL) \ error(MEPOO__TYPED_MEMPOOL_HAS_INCONSISTENT_STATE) \ error(MEPOO__TYPED_MEMPOOL_MANAGEMENT_SEGMENT_IS_BROKEN) \ - error(MEPOO__SEGMENT_CONTAINER_OVERFLOW) \ error(MEPOO__USER_WITH_MORE_THAN_ONE_WRITE_SEGMENT) \ error(MEPOO__SEGMENT_COULD_NOT_APPLY_POSIX_RIGHTS_TO_SHARED_MEMORY) \ error(MEPOO__SEGMENT_UNABLE_TO_CREATE_SHARED_MEMORY_OBJECT) \ From 933ff772c8b4c5a5db0947ff81f5da719c8bdff4 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Sat, 10 Apr 2021 22:52:17 +0200 Subject: [PATCH 062/127] iox-#482 Fix docu and doxygen Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/README.md | 4 ++-- iceoryx_examples/icecubetray/config_and_run_icecubetray.sh | 6 ++++-- iceoryx_examples/icecubetray/iox_cheeky_app.cpp | 7 ++++--- .../include/iceoryx_posh/internal/roudi/process.hpp | 3 +-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/icecubetray/README.md index 94dc28daf3c..d020665779a 100644 --- a/iceoryx_examples/icecubetray/README.md +++ b/iceoryx_examples/icecubetray/README.md @@ -2,11 +2,11 @@ ## Introduction -This example demonstrates how access rights can be applied to shared memory segments. +This example demonstrates how access rights can be applied to shared memory segments on Linux-based operating systems. It provides a custom RouDi, a radar and a display application. !!! hint - The access right feature is only supported on Linux-based operating systems + The access right feature is only supported on Linux and QNX ## Expected output diff --git a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh index 96a72e6fb8e..d797bd47890 100755 --- a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh @@ -16,6 +16,10 @@ # # SPDX-License-Identifier: Apache-2.0 +# ***NOTE*** +# This shell script is for Linux-based operating systems only. +# If you're using e.g QNX refer to the manual on how to set up groups, users and permissions + WORKSPACE=$(git rev-parse --show-toplevel) CONFIG="OFF" RUN="OFF" @@ -63,8 +67,6 @@ if [ "$CONFIG" == "ON" ] ; then sudo setcap cap_kill=ep $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments fi -# If you're using e.g Yocto or QNX refer to the manual on how to set up groups, users and permissions - if [ "$RUN" == "ON" ] ; then $tmux kill-server diff --git a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp index 0d9f25ff07e..2696127056f 100644 --- a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp +++ b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp @@ -27,11 +27,12 @@ int main() // initialize runtime iox::runtime::PoshRuntime::initRuntime(APP_NAME); - // Subscribers can be created without any readable shared memory segment, in this case no data will ever arrive + // When starting this app with the user 'notallowed' + + // 1) Subscribers can be created without any readable shared memory segment, in this case no data will ever arrive iox::popo::Subscriber subscriber({"Radar", "FrontLeft", "Object"}); - // When starting this app with the user 'notallowed', the publisher object can't be initalised correctly because - // 'notallowed' does not have write access + // 2) The publisher object can't be initalised correctly because 'notallowed' does not have write access iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); return (EXIT_FAILURE); diff --git a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp index 64a9f591ad5..aa9b6c99399 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp @@ -42,8 +42,7 @@ class Process /// application /// @param [in] name of the process; this is equal to the IPC channel name, which is used for communication /// @param [in] pid is the host system process id - /// @param [in] payloadDataSegmentMemoryManager is a pointer to the memory manager of the payload data segment for - /// this process + /// @param [in] user is user used in the operating system for this process /// @param [in] isMonitored indicates if the process should be monitored for being alive /// @param [in] dataSegmentId is an identifier for the shm data segment /// @param [in] sessionId is an ID generated by RouDi to prevent sending outdated IPC channel transmission From 2e0bfe46a91f2eae89ecd06d3c10f0ce5d214e5c Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Mon, 12 Apr 2021 21:58:49 +0200 Subject: [PATCH 063/127] iox-#482 Address review findings by clarifying capabilites and mentioning TOML Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/icecubetray/README.md index d020665779a..335cf4d5501 100644 --- a/iceoryx_examples/icecubetray/README.md +++ b/iceoryx_examples/icecubetray/README.md @@ -14,8 +14,7 @@ It provides a custom RouDi, a radar and a display application. ## Code walkthrough -The user _roudi_ does not need root access rights. However, it needs _CAP\_KILL_ capability or similar rights on -other POSIX operating system. RouDi needs to be able to send a _SIGKILL_ signal to the apps in case RouDi is shutdown. +RouDi needs to be able to send a _SIGKILL_ signal to the apps in case RouDi is shutdown. Hence, RouDi needs _CAP\_KILL_ capability or similar rights on other POSIX operating system. However, the user _roudi_ does not need root access rights. | Users | privileged group | unprivileged group | infotainment group | iceoryx group | |--------------|:----------------:|:------------------:|:------------------:|:------------------:| @@ -68,6 +67,7 @@ RouDi is built with two static shared memory segments _infotainment_ and _privil The `roudiConfig` is composed of a memory pool config called `mepooConfig`. When the segement is created, one needs to specific the reader group (first string), writer group (second string) as well as the `mepooConfig` (last parameter). +The access rights are solely based on user groups and not on users itself. All users in the reader group are allowed to read, but don't have write access. Users in the writer group have both read and write access. ```cpp iox::RouDiConfig_t roudiConfig; @@ -83,6 +83,10 @@ roudiConfig.m_sharedMemorySegments.push_back({"unprivileged", "privileged", mepo roudiConfig.m_sharedMemorySegments.push_back({"infotainment", "infotainment", mepooConfig}); ``` +!!! tip + Shared memory segment can also be configured via a + [TOML config](https://github.com/eclipse-iceoryx/iceoryx/blob/master/doc/website/advanced/configuration-guide.md#dynamic-configuration) file. + The radar app is started with the user _perception_ and is sending data into the _privileged_ shared memory segment. The display app is started with the user _infotainment_. It reads the topic `{"Radar", "FrontLeft", "Object"}` from the privileged segement and forwards it as a slighty modified topic `{"Radar", "HMI-Display", "Object"}`. Because the user _infotainment_ only has write access to the infotainment segment, the data is written to this segment. @@ -104,7 +108,8 @@ drwxr-xr-x 6 root root 460 Apr 6 15:53 .. ``` !!! note - Note the shared memory managment segment is always available for everyone to **read** and **write** + Note the shared memory managment segment (`iceoryx_mgmt`) is always available for everyone in the group `iceoryx` + to **read** and **write**. #### Not-working setup From e90a747b32553c212d2b4f7b4d1adc3555ceb0a4 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Tue, 13 Apr 2021 09:46:51 +0200 Subject: [PATCH 064/127] iox-#482 Replace std::terminate with std::exit Signed-off-by: Simon Hoinkis --- iceoryx_examples/waitset/README.md | 28 +++++++++---------- .../waitset/ice_waitset_basic.cpp | 4 +-- .../waitset/ice_waitset_gateway.cpp | 4 +-- .../waitset/ice_waitset_grouping.cpp | 6 ++-- .../waitset/ice_waitset_individual.cpp | 6 ++-- iceoryx_examples/waitset/ice_waitset_sync.cpp | 4 +-- .../waitset/ice_waitset_trigger.cpp | 4 +-- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/iceoryx_examples/waitset/README.md b/iceoryx_examples/waitset/README.md index a40b2b1007b..42de0eb9490 100644 --- a/iceoryx_examples/waitset/README.md +++ b/iceoryx_examples/waitset/README.md @@ -183,13 +183,13 @@ iox::popo::WaitSet<2> waitset; // attach shutdown trigger to waitset (needed to stop the processing loop) waitset.attachEvent(shutdownTrigger).or_else([](auto) { std::cerr << "failed to attach shutdown trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // attach subscriber to waitset waitset.attachState(subscriber, iox::popo::SubscriberState::HAS_DATA).or_else([](auto) { std::cerr << "failed to attach subscriber" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); ``` @@ -266,7 +266,7 @@ iox::popo::WaitSet waitset; waitset.attachEvent(shutdownTrigger).or_else([](auto) { std::cerr << "failed to attach shutdown trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); ``` @@ -284,7 +284,7 @@ for (auto i = 0; i < NUMBER_OF_SUBSCRIBERS; ++i) waitset.attachEvent(subscriber, iox::popo::SubscriberEvent::DATA_RECEIVED, 0, &subscriberCallback) .or_else([&](auto) { std::cerr << "failed to attach subscriber" << i << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); } ``` @@ -337,7 +337,7 @@ iox::popo::WaitSet waitset; waitset.attachEvent(shutdownTrigger).or_else([](auto) { std::cerr << "failed to attach shutdown trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); ``` @@ -361,7 +361,7 @@ for (auto i = 0; i < NUMBER_OF_SUBSCRIBERS / 2; ++i) waitset.attachEvent(subscriberVector[i], iox::popo::SubscriberState::HAS_DATA, FIRST_GROUP_ID) .or_else([&](auto) { std::cerr << "failed to attach subscriber" << i << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); } @@ -370,7 +370,7 @@ for (auto i = NUMBER_OF_SUBSCRIBERS / 2; i < NUMBER_OF_SUBSCRIBERS; ++i) waitset.attachEvent(subscriberVector[i], iox::popo::SubscriberState::HAS_DATA, SECOND_GROUP_ID) .or_else([&](auto) { std::cerr << "failed to attach subscriber" << i << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); } ``` @@ -432,7 +432,7 @@ iox::popo::WaitSet waitset<>; waitset.attachEvent(shutdownTrigger).or_else([](auto) { std::cerr << "failed to attach shutdown trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); ``` @@ -445,11 +445,11 @@ iox::popo::Subscriber subscriber2({"Radar", "FrontLeft", "Counter" waitset.attachEvent(subscriber1, iox::popo::SubscriberState::HAS_DATA).or_else([](auto) { std::cerr << "failed to attach subscriber1" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); waitset.attachEvent(subscriber2, iox::popo::SubscriberState::HAS_DATA).or_else([](auto) { std::cerr << "failed to attach subscriber2" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); ``` @@ -519,7 +519,7 @@ iox::popo::WaitSet<> waitset; // attach shutdownTrigger to handle CTRL+C waitset.attachEvent(shutdownTrigger).or_else([](auto) { std::cerr << "failed to attach shutdown trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); ``` @@ -531,7 +531,7 @@ eventId `0` and the callback `SomeClass::cyclicRun` iox::popo::UserTrigger cyclicTrigger; waitset.attachEvent(cyclicTrigger, 0U, &SomeClass::cyclicRun).or_else([](auto) { std::cerr << "failed to attach cyclic trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); ``` @@ -910,14 +910,14 @@ to the waitset and provide a callback for them. waitset->attachState(*triggerClass, MyTriggerClassStates::IS_ACTIVATED, ACTIVATE_ID, &callOnActivate) .or_else([](auto) { std::cerr << "failed to attach MyTriggerClassStates::IS_ACTIVATED state " << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); waitset ->attachEvent( *triggerClass, MyTriggerClassEvents::PERFORM_ACTION_CALLED, ACTION_ID, &MyTriggerClass::callOnAction) .or_else([](auto) { std::cerr << "failed to attach MyTriggerClassEvents::PERFORM_ACTION_CALLED event " << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); ``` diff --git a/iceoryx_examples/waitset/ice_waitset_basic.cpp b/iceoryx_examples/waitset/ice_waitset_basic.cpp index c58089a9027..3c7c8962bad 100644 --- a/iceoryx_examples/waitset/ice_waitset_basic.cpp +++ b/iceoryx_examples/waitset/ice_waitset_basic.cpp @@ -52,13 +52,13 @@ int main() // attach shutdown trigger to waitset (needed to stop the processing loop) waitset.attachEvent(shutdownTrigger).or_else([](auto) { std::cerr << "failed to attach shutdown trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // attach subscriber to waitset waitset.attachState(subscriber, iox::popo::SubscriberState::HAS_DATA).or_else([](auto) { std::cerr << "failed to attach subscriber" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); while (true) diff --git a/iceoryx_examples/waitset/ice_waitset_gateway.cpp b/iceoryx_examples/waitset/ice_waitset_gateway.cpp index 1e9251977f8..e9dedd0bc45 100644 --- a/iceoryx_examples/waitset/ice_waitset_gateway.cpp +++ b/iceoryx_examples/waitset/ice_waitset_gateway.cpp @@ -63,7 +63,7 @@ int main() // attach shutdownTrigger to handle CTRL+C waitset.attachEvent(shutdownTrigger).or_else([](auto) { std::cerr << "failed to attach shutdown trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // create subscriber and subscribe them to our service @@ -76,7 +76,7 @@ int main() waitset.attachEvent(subscriber, iox::popo::SubscriberEvent::DATA_RECEIVED, 0, &subscriberCallback) .or_else([&](auto) { std::cerr << "failed to attach subscriber" << i << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); } diff --git a/iceoryx_examples/waitset/ice_waitset_grouping.cpp b/iceoryx_examples/waitset/ice_waitset_grouping.cpp index 6f59a7023d8..881b9491211 100644 --- a/iceoryx_examples/waitset/ice_waitset_grouping.cpp +++ b/iceoryx_examples/waitset/ice_waitset_grouping.cpp @@ -46,7 +46,7 @@ int main() // attach shutdownTrigger to handle CTRL+C waitset.attachEvent(shutdownTrigger).or_else([](auto) { std::cerr << "failed to attach shutdown trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // create subscriber and subscribe them to our service @@ -68,7 +68,7 @@ int main() waitset.attachState(subscriberVector[i], iox::popo::SubscriberState::HAS_DATA, FIRST_GROUP_ID) .or_else([&](auto) { std::cerr << "failed to attach subscriber" << i << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); } @@ -78,7 +78,7 @@ int main() waitset.attachState(subscriberVector[i], iox::popo::SubscriberState::HAS_DATA, SECOND_GROUP_ID) .or_else([&](auto) { std::cerr << "failed to attach subscriber" << i << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); } diff --git a/iceoryx_examples/waitset/ice_waitset_individual.cpp b/iceoryx_examples/waitset/ice_waitset_individual.cpp index 3b9e92d177c..9a3dd8a1042 100644 --- a/iceoryx_examples/waitset/ice_waitset_individual.cpp +++ b/iceoryx_examples/waitset/ice_waitset_individual.cpp @@ -44,7 +44,7 @@ int main() // attach shutdownTrigger to handle CTRL+C waitset.attachEvent(shutdownTrigger).or_else([](auto) { std::cerr << "failed to attach shutdown trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // create two subscribers, subscribe to the service and attach them to the waitset @@ -53,11 +53,11 @@ int main() waitset.attachState(subscriber1, iox::popo::SubscriberState::HAS_DATA).or_else([](auto) { std::cerr << "failed to attach subscriber1" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); waitset.attachState(subscriber2, iox::popo::SubscriberState::HAS_DATA).or_else([](auto) { std::cerr << "failed to attach subscriber2" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // event loop diff --git a/iceoryx_examples/waitset/ice_waitset_sync.cpp b/iceoryx_examples/waitset/ice_waitset_sync.cpp index bdcdbdaf4b8..c78a9f2e3f8 100644 --- a/iceoryx_examples/waitset/ice_waitset_sync.cpp +++ b/iceoryx_examples/waitset/ice_waitset_sync.cpp @@ -54,7 +54,7 @@ int main() // attach shutdownTrigger to handle CTRL+C waitset.attachEvent(shutdownTrigger).or_else([](auto) { std::cerr << "failed to attach shutdown trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // create and attach the cyclicTrigger with a callback to @@ -62,7 +62,7 @@ int main() iox::popo::UserTrigger cyclicTrigger; waitset.attachEvent(cyclicTrigger, 0U, &SomeClass::cyclicRun).or_else([](auto) { std::cerr << "failed to attach cyclic trigger" << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // start a thread which triggers cyclicTrigger every second diff --git a/iceoryx_examples/waitset/ice_waitset_trigger.cpp b/iceoryx_examples/waitset/ice_waitset_trigger.cpp index acad6161924..ce69c5635a7 100644 --- a/iceoryx_examples/waitset/ice_waitset_trigger.cpp +++ b/iceoryx_examples/waitset/ice_waitset_trigger.cpp @@ -259,7 +259,7 @@ int main() waitset->attachState(*triggerClass, MyTriggerClassStates::IS_ACTIVATED, ACTIVATE_ID, &callOnActivate) .or_else([](auto) { std::cerr << "failed to attach MyTriggerClassStates::IS_ACTIVATED state " << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // attach the PERFORM_ACTION_CALLED event to the waitset and assign a callback waitset @@ -267,7 +267,7 @@ int main() *triggerClass, MyTriggerClassEvents::PERFORM_ACTION_CALLED, ACTION_ID, &MyTriggerClass::callOnAction) .or_else([](auto) { std::cerr << "failed to attach MyTriggerClassEvents::PERFORM_ACTION_CALLED event " << std::endl; - std::terminate(); + std::exit(EXIT_FAILURE); }); // start the event loop which is handling the events From 433b818a88ca1129d7f40370ee49bdf980ca5831 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Tue, 13 Apr 2021 11:51:28 +0200 Subject: [PATCH 065/127] iox-#482 Remove unnecessary parentheses Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/iox_cheeky_app.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp index 2696127056f..f8c0e185aa2 100644 --- a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp +++ b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp @@ -35,5 +35,5 @@ int main() // 2) The publisher object can't be initalised correctly because 'notallowed' does not have write access iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); - return (EXIT_FAILURE); + return EXIT_FAILURE; } From aae7b38104275b9ca029a30cfe125defad376684 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Tue, 13 Apr 2021 14:05:09 +0200 Subject: [PATCH 066/127] iox-#482 Fix ProcessManager test case Signed-off-by: Simon Hoinkis --- iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp b/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp index 011d34866a3..2e6c498d956 100644 --- a/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp +++ b/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp @@ -121,6 +121,10 @@ TEST_F(ProcessManager_test, HandleProcessShutdownPreparationRequestWorks) m_sut->registerProcess(m_processname, m_pid, m_user, m_isMonitored, 1U, 1U, m_versionInfo); auto user = iox::posix::PosixUser::getUserOfCurrentProcess().getName(); +<<<<<<< HEAD +======= + +>>>>>>> 9e1d2158f (iox-#482 Fix ProcessManager test case) auto payloadDataSegmentMemoryManager = m_roudiMemoryManager->segmentManager() .value() ->getSegmentInformationWithWriteAccessForUser(user) From 4040fc6646e6211070d3f2c87e0927bc8bdbe300 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Tue, 13 Apr 2021 16:01:42 +0200 Subject: [PATCH 067/127] iox-#482 Address review findings by re-writing parts of the icecubetray readme, optimising the shell script and extending the doxygen comments Signed-off-by: Simon Hoinkis --- iceoryx_examples/icecubetray/README.md | 87 +++++++++++++------ .../icecubetray/config_and_run_icecubetray.sh | 32 ++++--- .../icecubetray/iox_cheeky_app.cpp | 7 +- .../roudi_main_static_segements.cpp | 3 +- 4 files changed, 84 insertions(+), 45 deletions(-) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/icecubetray/README.md index 335cf4d5501..f35335d13f6 100644 --- a/iceoryx_examples/icecubetray/README.md +++ b/iceoryx_examples/icecubetray/README.md @@ -16,6 +16,8 @@ It provides a custom RouDi, a radar and a display application. RouDi needs to be able to send a _SIGKILL_ signal to the apps in case RouDi is shutdown. Hence, RouDi needs _CAP\_KILL_ capability or similar rights on other POSIX operating system. However, the user _roudi_ does not need root access rights. +The system user can be member of multiple system groups. This examples uses the following users and groups: + | Users | privileged group | unprivileged group | infotainment group | iceoryx group | |--------------|:----------------:|:------------------:|:------------------:|:------------------:| | perception | X | | | X | @@ -23,19 +25,28 @@ RouDi needs to be able to send a _SIGKILL_ signal to the apps in case RouDi is s | roudi | | | | X | | notallowed | | | | X | +!!! hint + To be able to use iceoryx communication all apps have to be in the group _iceoryx_. + ### Overview over the Apps and Shared Memory Segments +RouDi is built with two shared memory segments _infotainment_ and _privileged_. On startup, it creates these two shared +memory segments in the operating system. The _privileged_ segment requires the app to be started with the group +_privileged_ if it publishes data or with the _unprivileged_ group when data should only be received. +The _infotainment_ segment on the other hand requires only one group for reading and writing called _infotainment_. +See the [next chapter](#working-setup) for a detailed description on how to configure the shared memory segements. + ``` +-----------------------+ +--------------------+ | | +---------------------+ -| Radar App | | Privileged Shared | | Cheeky App | -| @perception | | Memory Segment | | @notallowed | -| | publish | | subscribe | # # | -| # | -------> | r group: unprivileged | ------> | # # | +| Radar App | | Privileged Shared | | Cheeky App | +| user: perception | | Memory Segment | | user: notallowed | +| | publish | | | # # | +| # | -------> | r group: unprivileged | | # # | | # | | w group: privileged | | # # | | # | | | | # # | -| # # | | | <------ | # # | -| # # | | | publish | # # | +| # # | | | | # # | +| # # | | | | # # | +--------------------+ | | +---------------------+ +-----------------------+ | @@ -47,8 +58,8 @@ RouDi needs to be able to send a _SIGKILL_ signal to the apps in case RouDi is s \ / +-----------------------+ | | +---------------------+ - | Infotainment Shared | | Display App | - | Memory Segment | publish | @infotainment | + | Infotainment Shared | | Display App | + | Memory Segment | publish | user: infotainment | | | <------ | | | r group: infotainment | | # | | w group: infotainment | | # | @@ -63,11 +74,7 @@ RouDi needs to be able to send a _SIGKILL_ signal to the apps in case RouDi is s #### Working setup -RouDi is built with two static shared memory segments _infotainment_ and _privileged_. The access rights of the segments are configured as depicted in the graphic above. - -The `roudiConfig` is composed of a memory pool config called `mepooConfig`. When the segement is created, one needs to -specific the reader group (first string), writer group (second string) as well as the `mepooConfig` (last parameter). -The access rights are solely based on user groups and not on users itself. All users in the reader group are allowed to read, but don't have write access. Users in the writer group have both read and write access. +Do the following to configure shared memory segments when building a custom RouDi: ```cpp iox::RouDiConfig_t roudiConfig; @@ -78,18 +85,28 @@ iox::mepoo::MePooConfig mepooConfig; // We only send very small data, just one mempool per segment mepooConfig.addMemPool({128, 1000}); -/// Create an Entry for a new Shared Memory Segment from the MempoolConfig and add it to the RouDiConfig +// Create an Entry for a new Shared Memory Segment from the MempoolConfig and add it to the RouDiConfig +// Parameters are {"ReaderGroup", "WriterGroup", MemoryPoolConfig} roudiConfig.m_sharedMemorySegments.push_back({"unprivileged", "privileged", mepooConfig}); roudiConfig.m_sharedMemorySegments.push_back({"infotainment", "infotainment", mepooConfig}); ``` +The `roudiConfig` is composed of a memory pool config called `mepooConfig`. When the segement is created, one needs to +specific the reader group (first string), writer group (second string) as well as the `mepooConfig` (last parameter). +The access rights are solely based on user groups and not on users itself. All users in the reader group are allowed to read, but don't have write access. Users in the writer group have both read and write access. + !!! tip Shared memory segment can also be configured via a [TOML config](https://github.com/eclipse-iceoryx/iceoryx/blob/master/doc/website/advanced/configuration-guide.md#dynamic-configuration) file. -The radar app is started with the user _perception_ and is sending data into the _privileged_ shared memory segment. +The radar app is started with the user _perception_, which is in the group _privileged_. Therefore it has write access +to the _privileged_ segment and is sending data into the _privileged_ shared memory segment. -The display app is started with the user _infotainment_. It reads the topic `{"Radar", "FrontLeft", "Object"}` from the privileged segement and forwards it as a slighty modified topic `{"Radar", "HMI-Display", "Object"}`. Because the user _infotainment_ only has write access to the infotainment segment, the data is written to this segment. +The display app is started with the user _infotainment_, which is in the group _infotainment_ and _unprivileged_. +Therefore it has read access to the _privileged_ segment. It reads the topic `{"Radar", "FrontLeft", "Object"}` from +the _privileged_ segment and forwards it as a slighty modified topic `{"Radar", "HMI-Display", "Object"}`. Because +the user _infotainment_ is only in the _infotainment_ and _unprivileged_ group, it only has write access to the +infotainment segment. Hence, the data is written to this segment. !!! hint It's advised to create per writer group only one shared memory segement (e.g. not two segements with `w: infotainment`). @@ -98,13 +115,32 @@ The display app is started with the user _infotainment_. It reads the topic `{"R The shared memory segments can be found under `/dev/shm` ``` -moss@reynholm:~$ ls -al /dev/shm -total 60268 -drwxrwxrwt 2 root root 100 Apr 7 19:54 . -drwxr-xr-x 6 root root 460 Apr 6 15:53 .. --rw-rw---- 1 roudi iceoryx 61383328 Apr 7 19:24 iceoryx_mgmt --rw-rw----+ 1 roudi iceoryx 160000 Apr 7 19:24 infotainment --rw-rw----+ 1 roudi iceoryx 160000 Apr 7 19:24 privileged +moss@reynholm:$ getfacl /dev/shm/* +# file: dev/shm/iceoryx_mgmt +# owner: roudi +# group: iceoryx +user::rw- +group::rw- +other::--- + +# file: dev/shm/infotainment +# owner: roudi +# group: iceoryx +user::rw- +group::rw- +group:infotainment:rw- +mask::rw- +other::--- + +# file: dev/shm/privileged +# owner: roudi +# group: iceoryx +user::rw- +group::rw- +group:privileged:rw- +group:unprivileged:r-- +mask::rw- +other::-- ``` !!! note @@ -113,9 +149,10 @@ drwxr-xr-x 6 root root 460 Apr 6 15:53 .. #### Not-working setup -The cheeky app is started with the user _notallowed_. It has neither write nor read access to any shared memory segment. Hence, RouDi will print a warning in this case. +The cheeky app is started with the user _notallowed_. This user is not in any group that would allow him either read +or write access to one of the shared memory segments. Hence, RouDi will print a warning in this case. -Despite having no read access, subscribers can still be created. In this case no data will ever arrive. +Despite having no read access, subscribers can still be created. ```cpp iox::popo::Subscriber subscriber({"Radar", "FrontLeft", "Object"}); diff --git a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh index d797bd47890..a84fd22ef4e 100755 --- a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh +++ b/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh @@ -24,7 +24,7 @@ WORKSPACE=$(git rev-parse --show-toplevel) CONFIG="OFF" RUN="OFF" SESSION=icecubetray -tmux="tmux -2 -q" +TMUX="TMUX -2 -q" while (( "$#" )); do case "$1" in @@ -56,42 +56,40 @@ if [ "$CONFIG" == "ON" ] ; then sudo useradd -M notallowed # Assign users to group and disable login - sudo usermod -L perception -a -G privileged - sudo usermod -L infotainment -a -G unprivileged - sudo usermod -L perception -a -G iceoryx - sudo usermod -L infotainment -a -G iceoryx - sudo usermod -L roudi -a -G iceoryx - sudo usermod -L notallowed -a -G iceoryx + sudo usermod -L perception -a -G privileged,iceoryx -s /sbin/nologin + sudo usermod -L infotainment -a -G unprivileged,iceoryx -s /sbin/nologin + sudo usermod -L roudi -a -G iceoryx -s /sbin/nologin + sudo usermod -L notallowed -a -G iceoryx -s /sbin/nologin # Allow RouDi to send SIGKILL to other apps sudo setcap cap_kill=ep $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments fi if [ "$RUN" == "ON" ] ; then - $tmux kill-server + $TMUX kill-server - $tmux has-session -t $SESSION + $TMUX has-session -t $SESSION if [ $? -eq 0 ]; then echo "Session $SESSION already exists. Attaching to session." - $tmux attach -t $SESSION + $TMUX attach -t $SESSION exit 0; fi - command -v tmux >/dev/null 2>&1 || { echo >&2 "tmux is not installed but required. Trying to install it..."; sudo apt-get install tmux; } + command -v $TMUX >/dev/null 2>&1 || { echo >&2 "TMUX is not installed but required. Trying to install it..."; sudo apt-get install TMUX; } - $tmux new-session -d -s $SESSION + $TMUX new-session -d -s $SESSION # Start custom RouDi in 'iceoryx' group - $tmux new-window -a -t $SESSION 'sudo -u roudi -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments' + $TMUX new-window -a -t $SESSION 'sudo -u roudi -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments' # Start perception app as 'perception' user - $tmux split-window -t 0 -h 'sudo -u perception -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-radar' + $TMUX split-window -t 0 -h 'sudo -u perception -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-radar' # Start display app as 'infotainment' user - $tmux split-window -t 1 -v 'sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-display' + $TMUX split-window -t 1 -v 'sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-display' # Start cheeky app as 'notallowed' user - $tmux split-window -t 0 -v 'sudo -u notallowed -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-cheeky' + $TMUX split-window -t 0 -v 'sudo -u notallowed -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-cheeky' - $tmux attach -t $SESSION + $TMUX attach -t $SESSION fi diff --git a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp index f8c0e185aa2..a934c0be924 100644 --- a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp +++ b/iceoryx_examples/icecubetray/iox_cheeky_app.cpp @@ -29,10 +29,13 @@ int main() // When starting this app with the user 'notallowed' - // 1) Subscribers can be created without any readable shared memory segment, in this case no data will ever arrive + // 1) Subscribers can be created without any readable shared memory segment + /// @todo currently segfaults, in this case no data should ever arrive iox::popo::Subscriber subscriber({"Radar", "FrontLeft", "Object"}); - // 2) The publisher object can't be initalised correctly because 'notallowed' does not have write access + // 2) The publisher object can't be initalised correctly because the user 'notallowed' isn't in any group which has + // write access to any shared memory segment. + // The error POSH__RUNTIME_NO_WRITABLE_SHM_SEGMENT will be reported and programm execution will end iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); return EXIT_FAILURE; diff --git a/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp b/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp index daf4ca82a38..4b2c2ec144a 100644 --- a/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp +++ b/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp @@ -40,7 +40,8 @@ int main(int argc, char* argv[]) // We only send very small data, just one mempool per segment mepooConfig.addMemPool({128, 1000}); - /// Create an entry for a new shared memory segment from the mempooConfig and add it to the roudiConfig + // Create an entry for a new shared memory segment from the mempooConfig and add it to the roudiConfig + // Parameters are {"ReaderGroup", "WriterGroup", MemoryPoolConfig} roudiConfig.m_sharedMemorySegments.push_back({"unprivileged", "privileged", mepooConfig}); roudiConfig.m_sharedMemorySegments.push_back({"infotainment", "infotainment", mepooConfig}); From eac018f5aef641a434f0366b6356bfbcafbf0962 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Tue, 13 Apr 2021 16:35:10 +0200 Subject: [PATCH 068/127] iox-#482 Address review findings by applying const correctness Signed-off-by: Simon Hoinkis --- .../include/iceoryx_posh/internal/mepoo/segment_manager.hpp | 5 +++-- .../include/iceoryx_posh/internal/mepoo/segment_manager.inl | 4 ++-- iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp | 2 +- .../include/iceoryx_posh/internal/roudi/process_manager.hpp | 2 +- iceoryx_posh/source/roudi/process.cpp | 2 +- iceoryx_posh/source/roudi/process_manager.cpp | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp index 78f1214d4df..3f0675cd8af 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp @@ -1,4 +1,5 @@ // Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. +// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -85,8 +86,8 @@ class SegmentManager using SegmentMappingContainer = cxx::vector; - SegmentMappingContainer getSegmentMappings(posix::PosixUser user) noexcept; - SegmentUserInformation getSegmentInformationWithWriteAccessForUser(posix::PosixUser user) noexcept; + SegmentMappingContainer getSegmentMappings(const posix::PosixUser& user) noexcept; + SegmentUserInformation getSegmentInformationWithWriteAccessForUser(const posix::PosixUser& user) noexcept; static uint64_t requiredManagementMemorySize(const SegmentConfig& config) noexcept; static uint64_t requiredChunkMemorySize(const SegmentConfig& config) noexcept; diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl index aa6ef61c07f..bb35ce97e10 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl @@ -49,7 +49,7 @@ inline void SegmentManager::createSegment(const SegmentConfig::Segm template inline typename SegmentManager::SegmentMappingContainer -SegmentManager::getSegmentMappings(posix::PosixUser user) noexcept +SegmentManager::getSegmentMappings(const posix::PosixUser& user) noexcept { // get all the groups the user is in auto groupContainer = user.getGroups(); @@ -107,7 +107,7 @@ SegmentManager::getSegmentMappings(posix::PosixUser user) noexcept template inline typename SegmentManager::SegmentUserInformation -SegmentManager::getSegmentInformationWithWriteAccessForUser(posix::PosixUser user) noexcept +SegmentManager::getSegmentInformationWithWriteAccessForUser(const posix::PosixUser& user) noexcept { auto groupContainer = user.getGroups(); diff --git a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp index aa9b6c99399..e528a3963cd 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process.hpp @@ -48,7 +48,7 @@ class Process /// @param [in] sessionId is an ID generated by RouDi to prevent sending outdated IPC channel transmission Process(const RuntimeName_t& name, const uint32_t pid, - posix::PosixUser user, + const posix::PosixUser& user, const bool isMonitored, const uint64_t sessionId) noexcept; diff --git a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp index bc8df5b9d2d..e2a2ddae98b 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/roudi/process_manager.hpp @@ -158,7 +158,7 @@ class ProcessManager : public ProcessManagerInterface /// @return Returns if the process could be added successfully. bool addProcess(const RuntimeName_t& name, const uint32_t pid, - posix::PosixUser user, + const posix::PosixUser& user, const bool isMonitored, const int64_t transmissionTimestamp, const uint64_t sessionId, diff --git a/iceoryx_posh/source/roudi/process.cpp b/iceoryx_posh/source/roudi/process.cpp index 894d46f2148..d4ee75f14de 100644 --- a/iceoryx_posh/source/roudi/process.cpp +++ b/iceoryx_posh/source/roudi/process.cpp @@ -27,7 +27,7 @@ namespace roudi { Process::Process(const RuntimeName_t& name, const uint32_t pid, - posix::PosixUser user, + const posix::PosixUser& user, const bool isMonitored, const uint64_t sessionId) noexcept : m_pid(pid) diff --git a/iceoryx_posh/source/roudi/process_manager.cpp b/iceoryx_posh/source/roudi/process_manager.cpp index 2c201bda114..5445480be9b 100644 --- a/iceoryx_posh/source/roudi/process_manager.cpp +++ b/iceoryx_posh/source/roudi/process_manager.cpp @@ -259,7 +259,7 @@ bool ProcessManager::registerProcess(const RuntimeName_t& name, bool ProcessManager::addProcess(const RuntimeName_t& name, const uint32_t pid, - posix::PosixUser user, + const posix::PosixUser& user, const bool isMonitored, const int64_t transmissionTimestamp, const uint64_t sessionId, From 319de11805eb4341519a0f99f158175fec0bce0c Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Tue, 13 Apr 2021 16:58:38 +0200 Subject: [PATCH 069/127] iox-#482 Address review findings by replacing MemoryManager* with std::reference_wrapper inside cxx::optional Signed-off-by: Simon Hoinkis --- .../include/iceoryx_posh/internal/mepoo/segment_manager.hpp | 2 +- .../include/iceoryx_posh/internal/mepoo/segment_manager.inl | 2 +- iceoryx_posh/source/roudi/process_manager.cpp | 2 +- iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp | 2 +- .../test/moduletests/test_mepoo_segment_management.cpp | 6 +++--- iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp | 2 +- .../test/moduletests/test_roudi_process_manager.cpp | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp index 3f0675cd8af..c27b8aa392b 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.hpp @@ -80,7 +80,7 @@ class SegmentManager struct SegmentUserInformation { - cxx::optional m_memoryManager; + cxx::optional> m_memoryManager; uint64_t m_segmentID; }; diff --git a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl index bb35ce97e10..651299b7f0c 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl +++ b/iceoryx_posh/include/iceoryx_posh/internal/mepoo/segment_manager.inl @@ -120,7 +120,7 @@ SegmentManager::getSegmentInformationWithWriteAccessForUser(const p { if (segment.getWriterGroup() == groupID) { - segmentInfo.m_memoryManager = &segment.getMemoryManager(); + segmentInfo.m_memoryManager = segment.getMemoryManager(); segmentInfo.m_segmentID = segment.getSegmentId(); return segmentInfo; } diff --git a/iceoryx_posh/source/roudi/process_manager.cpp b/iceoryx_posh/source/roudi/process_manager.cpp index 5445480be9b..10021bb6168 100644 --- a/iceoryx_posh/source/roudi/process_manager.cpp +++ b/iceoryx_posh/source/roudi/process_manager.cpp @@ -541,7 +541,7 @@ void ProcessManager::addPublisherForProcess(const RuntimeName_t& name, } auto maybePublisher = m_portManager.acquirePublisherPortData( - service, publisherOptions, name, segmentInfo.m_memoryManager.value(), portConfigInfo); + service, publisherOptions, name, &segmentInfo.m_memoryManager.value().get(), portConfigInfo); if (!maybePublisher.has_error()) { diff --git a/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp b/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp index e7d4492600b..c8ca0078289 100644 --- a/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp +++ b/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp @@ -253,7 +253,7 @@ class Mepoo_IntegrationTest : public Test ->getSegmentInformationWithWriteAccessForUser(currentUser.getName()) .m_memoryManager; ASSERT_TRUE(memoryManager.has_value()); - m_roudiEnv->m_roudiApp->m_mempoolIntrospection.copyMemPoolInfo(*memoryManager.value(), mempoolInfo); + m_roudiEnv->m_roudiApp->m_mempoolIntrospection.copyMemPoolInfo(memoryManager.value().get(), mempoolInfo); // internally, the chunks are adjusted to the additional management information; // this needs to be subtracted to be able to compare to the configured sizes diff --git a/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp b/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp index de3aeeacea1..1204ed9257f 100644 --- a/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp +++ b/iceoryx_posh/test/moduletests/test_mepoo_segment_management.cpp @@ -154,10 +154,10 @@ TEST_F(SegmentManager_test, ADD_TEST_WITH_ADDITIONAL_USER(getMemoryManagerForUse { auto memoryManager = sut.getSegmentInformationWithWriteAccessForUser({"iox_roudi_test2"}).m_memoryManager; ASSERT_TRUE(memoryManager.has_value()); - ASSERT_THAT(memoryManager.value()->getNumberOfMemPools(), Eq(2u)); + ASSERT_THAT(memoryManager.value().get().getNumberOfMemPools(), Eq(2u)); - auto poolInfo1 = memoryManager.value()->getMemPoolInfo(0); - auto poolInfo2 = memoryManager.value()->getMemPoolInfo(1); + auto poolInfo1 = memoryManager.value().get().getMemPoolInfo(0); + auto poolInfo2 = memoryManager.value().get().getMemPoolInfo(1); EXPECT_THAT(poolInfo1.m_numChunks, Eq(5u)); EXPECT_THAT(poolInfo2.m_numChunks, Eq(7u)); } diff --git a/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp b/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp index d3834204bad..405b0f53757 100644 --- a/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp +++ b/iceoryx_posh/test/moduletests/test_roudi_portmanager.cpp @@ -86,7 +86,7 @@ class PortManager_test : public Test auto segmentInfo = m_roudiMemoryManager->segmentManager().value()->getSegmentInformationWithWriteAccessForUser(user); ASSERT_TRUE(segmentInfo.m_memoryManager.has_value()); - m_payloadDataSegmentMemoryManager = segmentInfo.m_memoryManager.value(); + m_payloadDataSegmentMemoryManager = &segmentInfo.m_memoryManager.value().get(); // clearing the introspection, is not in d'tor -> SEGFAULT in delete sporadically m_portManager->stopPortIntrospection(); diff --git a/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp b/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp index 2e6c498d956..630e72ba588 100644 --- a/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp +++ b/iceoryx_posh/test/moduletests/test_roudi_process_manager.cpp @@ -139,7 +139,7 @@ TEST_F(ProcessManager_test, HandleProcessShutdownPreparationRequestWorks) ->acquirePublisherPortData({1U, 1U, 1U}, publisherOptions, m_processname, - payloadDataSegmentMemoryManager.value(), + &payloadDataSegmentMemoryManager.value().get(), PortConfigInfo()) .value()); From f11a1b192c4a8e54df4763106d7cf2343c10925f Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Tue, 13 Apr 2021 17:53:23 +0200 Subject: [PATCH 070/127] iox-#482 Rename icecubetray to ice_access_control Signed-off-by: Simon Hoinkis --- doc/website/getting-started/examples/.pages | 2 +- .../{icecubetray.md => ice_access_control.md} | 2 +- iceoryx_examples/README.md | 30 +++++++++---------- .../CMakeLists.txt | 4 +-- .../README.md | 4 +-- .../config_and_run_ice_access_control.sh} | 12 ++++---- .../iox_cheeky_app.cpp | 0 .../iox_display_app.cpp | 0 .../iox_radar_app.cpp | 0 .../roudi_main_static_segements.cpp | 0 .../topic_data.hpp | 6 ++-- ....py => test_ice_access_control_example.py} | 16 +++++----- iceoryx_meta/CMakeLists.txt | 2 +- tools/iceoryx_build_test.sh | 2 +- 14 files changed, 40 insertions(+), 40 deletions(-) rename doc/website/getting-started/examples/{icecubetray.md => ice_access_control.md} (54%) rename iceoryx_examples/{icecubetray => ice_access_control}/CMakeLists.txt (97%) rename iceoryx_examples/{icecubetray => ice_access_control}/README.md (97%) rename iceoryx_examples/{icecubetray/config_and_run_icecubetray.sh => ice_access_control/config_and_run_ice_access_control.sh} (88%) rename iceoryx_examples/{icecubetray => ice_access_control}/iox_cheeky_app.cpp (100%) rename iceoryx_examples/{icecubetray => ice_access_control}/iox_display_app.cpp (100%) rename iceoryx_examples/{icecubetray => ice_access_control}/iox_radar_app.cpp (100%) rename iceoryx_examples/{icecubetray => ice_access_control}/roudi_main_static_segements.cpp (100%) rename iceoryx_examples/{icecubetray => ice_access_control}/topic_data.hpp (84%) rename iceoryx_integrationtest/iceoryx_integrationtest/{test_icecubetray_example.py => test_ice_access_control_example.py} (86%) diff --git a/doc/website/getting-started/examples/.pages b/doc/website/getting-started/examples/.pages index 3c2ef4310b9..5bfa60c1352 100644 --- a/doc/website/getting-started/examples/.pages +++ b/doc/website/getting-started/examples/.pages @@ -10,6 +10,6 @@ nav: - waitset_in_c.md - iceensemble.md - singleprocess.md - - icecubetray.md + - ice_access_control.md - iceperf.md - icecrystal.md diff --git a/doc/website/getting-started/examples/icecubetray.md b/doc/website/getting-started/examples/ice_access_control.md similarity index 54% rename from doc/website/getting-started/examples/icecubetray.md rename to doc/website/getting-started/examples/ice_access_control.md index 884ad4c638d..80000a4f62e 100644 --- a/doc/website/getting-started/examples/icecubetray.md +++ b/doc/website/getting-started/examples/ice_access_control.md @@ -2,4 +2,4 @@ title: Configuring access rights for shared memory segments --- -{! ./../iceoryx_examples/icecubetray/README.md !} +{! ./../iceoryx_examples/ice_access_control/README.md !} diff --git a/iceoryx_examples/README.md b/iceoryx_examples/README.md index fc5c232253f..1ad33d13d70 100644 --- a/iceoryx_examples/README.md +++ b/iceoryx_examples/README.md @@ -1,17 +1,17 @@ # List of examples -| Example | Description | Level | -|:---------------------------------------|:--------------------------------------------------------------------------|:-------------------| -|[icehello](./icehello/) | Sending data to another process | :star: | -|[icedelivery](./icedelivery/) | Sending and receiving data using C++ | :star: | -|[icedelivery_in_c](./icedelivery_in_c/) | Sending and receiving data using C | :star: | -|[iceoptions](./iceoptions/) | Configuring pub/sub settings like history cache size or startup behaviour | :star: | -|[callbacks](./callbacks/) | Implementing event triggered callbacks using C++ | :star::star: | -|[callbacks_in_c](./callbacks_in_c/) | Implementing event triggered callbacks using C | :star::star: | -|[waitset](./waitset/) | Waiting for events like arrival of data using C++ | :star::star: | -|[waitset_in_c](./waitset_in_c/) | Waiting for events like arrival of data using C | :star::star: | -|[iceensemble](./iceensemble/) | Using multiple publishers for one topic | :star::star: | -|[singleprocess](./singleprocess/) | Communicating in a single process between threads | :star::star: | -|[icecubetray](./icecubetray/) | Configuring access rights for shared memory segments | :star::star::star: | -|[iceperf](./iceperf/) | Measuring the latency of different IPC mechanisms | :star::star::star: | -|[icecrystal](./icecrystal/) | Using the introspection client for debugging | :star::star::star: | +| Example | Description | Level | +|:-------------------------------------------|:--------------------------------------------------------------------------|:-------------------| +|[icehello](./icehello/) | Sending data to another process | :star: | +|[icedelivery](./icedelivery/) | Sending and receiving data using C++ | :star: | +|[icedelivery_in_c](./icedelivery_in_c/) | Sending and receiving data using C | :star: | +|[iceoptions](./iceoptions/) | Configuring pub/sub settings like history cache size or startup behaviour | :star: | +|[callbacks](./callbacks/) | Implementing event triggered callbacks using C++ | :star::star: | +|[callbacks_in_c](./callbacks_in_c/) | Implementing event triggered callbacks using C | :star::star: | +|[waitset](./waitset/) | Waiting for events like arrival of data using C++ | :star::star: | +|[waitset_in_c](./waitset_in_c/) | Waiting for events like arrival of data using C | :star::star: | +|[iceensemble](./iceensemble/) | Using multiple publishers for one topic | :star::star: | +|[singleprocess](./singleprocess/) | Communicating in a single process between threads | :star::star: | +|[ice_access_control](./ice_access_control/) | Configuring access rights for shared memory segments | :star::star::star: | +|[iceperf](./iceperf/) | Measuring the latency of different IPC mechanisms | :star::star::star: | +|[icecrystal](./icecrystal/) | Using the introspection client for debugging | :star::star::star: | diff --git a/iceoryx_examples/icecubetray/CMakeLists.txt b/iceoryx_examples/ice_access_control/CMakeLists.txt similarity index 97% rename from iceoryx_examples/icecubetray/CMakeLists.txt rename to iceoryx_examples/ice_access_control/CMakeLists.txt index 1c734c28459..6081ef93e50 100644 --- a/iceoryx_examples/icecubetray/CMakeLists.txt +++ b/iceoryx_examples/ice_access_control/CMakeLists.txt @@ -14,9 +14,9 @@ # # SPDX-License-Identifier: Apache-2.0 -# Build icecubetray example +# Build ice_access_control example cmake_minimum_required(VERSION 3.5) -project(example_icecubetray) +project(example_ice_access_control) option(TOML_CONFIG "TOML support for RouDi with dynamic configuration" OFF) diff --git a/iceoryx_examples/icecubetray/README.md b/iceoryx_examples/ice_access_control/README.md similarity index 97% rename from iceoryx_examples/icecubetray/README.md rename to iceoryx_examples/ice_access_control/README.md index f35335d13f6..eb5d57acfd9 100644 --- a/iceoryx_examples/icecubetray/README.md +++ b/iceoryx_examples/ice_access_control/README.md @@ -1,4 +1,4 @@ -# icecubetray +# ice_access_control ## Introduction @@ -165,5 +165,5 @@ iox::popo::Publisher publisher({"Radar", "FrontLeft", "Object"}); ```
-[Check out icecubetray on GitHub :fontawesome-brands-github:](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/icecubetray){ .md-button } +[Check out ice_access_control on GitHub :fontawesome-brands-github:](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/ice_access_control){ .md-button }
diff --git a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh b/iceoryx_examples/ice_access_control/config_and_run_ice_access_control.sh similarity index 88% rename from iceoryx_examples/icecubetray/config_and_run_icecubetray.sh rename to iceoryx_examples/ice_access_control/config_and_run_ice_access_control.sh index a84fd22ef4e..e177a6305f5 100755 --- a/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh +++ b/iceoryx_examples/ice_access_control/config_and_run_ice_access_control.sh @@ -23,7 +23,7 @@ WORKSPACE=$(git rev-parse --show-toplevel) CONFIG="OFF" RUN="OFF" -SESSION=icecubetray +SESSION=ice_access_control TMUX="TMUX -2 -q" while (( "$#" )); do @@ -62,7 +62,7 @@ if [ "$CONFIG" == "ON" ] ; then sudo usermod -L notallowed -a -G iceoryx -s /sbin/nologin # Allow RouDi to send SIGKILL to other apps - sudo setcap cap_kill=ep $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments + sudo setcap cap_kill=ep $WORKSPACE/build/iceoryx_examples/ice_access_control/iox-cpp-roudi-static-segments fi if [ "$RUN" == "ON" ] ; then @@ -80,16 +80,16 @@ if [ "$RUN" == "ON" ] ; then $TMUX new-session -d -s $SESSION # Start custom RouDi in 'iceoryx' group - $TMUX new-window -a -t $SESSION 'sudo -u roudi -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-roudi-static-segments' + $TMUX new-window -a -t $SESSION 'sudo -u roudi -g iceoryx -- $WORKSPACE/build/iceoryx_examples/ice_access_control/iox-cpp-roudi-static-segments' # Start perception app as 'perception' user - $TMUX split-window -t 0 -h 'sudo -u perception -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-radar' + $TMUX split-window -t 0 -h 'sudo -u perception -g iceoryx -- $WORKSPACE/build/iceoryx_examples/ice_access_control/iox-cpp-radar' # Start display app as 'infotainment' user - $TMUX split-window -t 1 -v 'sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-display' + $TMUX split-window -t 1 -v 'sudo -u infotainment -g iceoryx -- $WORKSPACE/build/iceoryx_examples/ice_access_control/iox-cpp-display' # Start cheeky app as 'notallowed' user - $TMUX split-window -t 0 -v 'sudo -u notallowed -g iceoryx -- $WORKSPACE/build/iceoryx_examples/icecubetray/iox-cpp-cheeky' + $TMUX split-window -t 0 -v 'sudo -u notallowed -g iceoryx -- $WORKSPACE/build/iceoryx_examples/ice_access_control/iox-cpp-cheeky' $TMUX attach -t $SESSION fi diff --git a/iceoryx_examples/icecubetray/iox_cheeky_app.cpp b/iceoryx_examples/ice_access_control/iox_cheeky_app.cpp similarity index 100% rename from iceoryx_examples/icecubetray/iox_cheeky_app.cpp rename to iceoryx_examples/ice_access_control/iox_cheeky_app.cpp diff --git a/iceoryx_examples/icecubetray/iox_display_app.cpp b/iceoryx_examples/ice_access_control/iox_display_app.cpp similarity index 100% rename from iceoryx_examples/icecubetray/iox_display_app.cpp rename to iceoryx_examples/ice_access_control/iox_display_app.cpp diff --git a/iceoryx_examples/icecubetray/iox_radar_app.cpp b/iceoryx_examples/ice_access_control/iox_radar_app.cpp similarity index 100% rename from iceoryx_examples/icecubetray/iox_radar_app.cpp rename to iceoryx_examples/ice_access_control/iox_radar_app.cpp diff --git a/iceoryx_examples/icecubetray/roudi_main_static_segements.cpp b/iceoryx_examples/ice_access_control/roudi_main_static_segements.cpp similarity index 100% rename from iceoryx_examples/icecubetray/roudi_main_static_segements.cpp rename to iceoryx_examples/ice_access_control/roudi_main_static_segements.cpp diff --git a/iceoryx_examples/icecubetray/topic_data.hpp b/iceoryx_examples/ice_access_control/topic_data.hpp similarity index 84% rename from iceoryx_examples/icecubetray/topic_data.hpp rename to iceoryx_examples/ice_access_control/topic_data.hpp index 6906492c310..73e50ca8212 100644 --- a/iceoryx_examples/icecubetray/topic_data.hpp +++ b/iceoryx_examples/ice_access_control/topic_data.hpp @@ -13,8 +13,8 @@ // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 -#ifndef IOX_EXAMPLES_ICECUBETRAY_TOPIC_DATA_HPP -#define IOX_EXAMPLES_ICECUBETRAY_TOPIC_DATA_HPP +#ifndef IOX_EXAMPLES_ICE_ACCESS_CONTROL_TOPIC_DATA_HPP +#define IOX_EXAMPLES_ICE_ACCESS_CONTROL_TOPIC_DATA_HPP #include @@ -34,4 +34,4 @@ struct RadarObject double z = 0.0; }; -#endif // IOX_EXAMPLES_ICECUBETRAY_TOPIC_DATA_HPP +#endif // IOX_EXAMPLES_ICE_ACCESS_CONTROL_TOPIC_DATA_HPP diff --git a/iceoryx_integrationtest/iceoryx_integrationtest/test_icecubetray_example.py b/iceoryx_integrationtest/iceoryx_integrationtest/test_ice_access_control_example.py similarity index 86% rename from iceoryx_integrationtest/iceoryx_integrationtest/test_icecubetray_example.py rename to iceoryx_integrationtest/iceoryx_integrationtest/test_ice_access_control_example.py index c850365b164..3bcf84dbb78 100755 --- a/iceoryx_integrationtest/iceoryx_integrationtest/test_icecubetray_example.py +++ b/iceoryx_integrationtest/iceoryx_integrationtest/test_ice_access_control_example.py @@ -24,7 +24,7 @@ import pytest -# @brief Test goal: "Integrationtest for the icecubetray example of iceoryx" +# @brief Test goal: "Integrationtest for the ice_access_control example of iceoryx" # @pre setup ROS2 launch executable for RouDi (debug mode) the example processes # @post check if all applications return exitcode 0 (success) after test run @pytest.mark.launch_test @@ -34,7 +34,7 @@ def generate_test_description(): colcon_prefix_path = os.environ.get('COLCON_PREFIX_PATH', '') # Configure users and groups necessary to run this integration test - subprocess.call(['sh', '$(git rev-parse --show-toplevel)/iceoryx_examples/icecubetray/config_and_run_icecubetray.sh config']) + subprocess.call(['sh', '$(git rev-parse --show-toplevel)/iceoryx_examples/ice_access_control/config_and_run_ice_access_control.sh config']) executable_list = ['iox-cpp-display', 'iox-cpp-radar', 'iox-cpp-cheeky'] user_list = ['infotainment', 'perception', 'notallowed'] @@ -53,7 +53,7 @@ def generate_test_description(): roudi_executable = os.path.join( colcon_prefix_path, - 'iceoryx_icecubetray/bin/', + 'iceoryx_ice_access_control/bin/', 'iox-cpp-roudi-static-segments' ) roudi_process = launch.actions.ExecuteProcess( @@ -70,25 +70,25 @@ def generate_test_description(): ]), {'iox-cpp-radar': process_list[0], 'iox-cpp-display': process_list[1], 'iox-cpp-cheeky': process_list[2]} -class TestIcecubetrayExample(unittest.TestCase): +class TestIceAccessControlExample(unittest.TestCase): def test_roudi_ready(self, proc_output): proc_output.assertWaitFor( 'RouDi is ready for clients', timeout=45, stream='stdout') - def test_icecubetray_radar(self, proc_output): + def test_ice_access_control_radar(self, proc_output): proc_output.assertWaitFor( 'iox-cpp-radar sent value: 10', timeout=45, stream='stdout') - def test_icecubetray_display(self, proc_output): + def test_ice_access_control_display(self, proc_output): proc_output.assertWaitFor( 'iox-cpp-display sending value: 10', timeout=45, stream='stdout') - def test_icecubetray_cheeky(self, proc_output): + def test_ice_access_control_cheeky(self, proc_output): proc_output.assertWaitFor( 'RouDi did not find a writable shared memory segment for the current user.', timeout=45, stream='stdout') @ launch_testing.post_shutdown_test() -class TestIcecubetraySetExampleExitCodes(unittest.TestCase): +class Testice_access_controlSetExampleExitCodes(unittest.TestCase): def test_exit_code(self, proc_info): launch_testing.asserts.assertExitCodes(proc_info) diff --git a/iceoryx_meta/CMakeLists.txt b/iceoryx_meta/CMakeLists.txt index 398552de6ea..726b7db0932 100644 --- a/iceoryx_meta/CMakeLists.txt +++ b/iceoryx_meta/CMakeLists.txt @@ -67,7 +67,7 @@ if(EXAMPLES) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../iceoryx_examples/waitset ${CMAKE_BINARY_DIR}/iceoryx_examples/waitset) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../iceoryx_examples/callbacks ${CMAKE_BINARY_DIR}/iceoryx_examples/callbacks) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../iceoryx_examples/singleprocess ${CMAKE_BINARY_DIR}/iceoryx_examples/singleprocess) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../iceoryx_examples/icecubetray ${CMAKE_BINARY_DIR}/iceoryx_examples/icecubetray) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../iceoryx_examples/ice_access_control ${CMAKE_BINARY_DIR}/iceoryx_examples/ice_access_control) endif() include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/package/package.cmake) diff --git a/tools/iceoryx_build_test.sh b/tools/iceoryx_build_test.sh index 15c05a049cb..34b00c6fd55 100755 --- a/tools/iceoryx_build_test.sh +++ b/tools/iceoryx_build_test.sh @@ -49,7 +49,7 @@ EXAMPLE_FLAG="OFF" BUILD_ALL_FLAG="OFF" BUILD_SHARED="OFF" TOML_FLAG="ON" -EXAMPLES="callbacks callbacks_in_c icedelivery singleprocess waitset icehello iceoptions icecubetray" +EXAMPLES="callbacks callbacks_in_c icedelivery singleprocess waitset icehello iceoptions ice_access_control" COMPONENTS="iceoryx_posh iceoryx_utils iceoryx_introspection iceoryx_binding_c iceoryx_component iceoryx_dds" TOOLCHAIN_FILE="" From 5892516fb924f71a3da2081bb787bfec88fc4ba4 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Mon, 12 Apr 2021 10:37:12 +0200 Subject: [PATCH 071/127] iox-#482 Update iceoryx logo, set color palettem, add custom font and restructure front page Signed-off-by: Simon Hoinkis --- README.md | 2 +- doc/website/getting-started/.pages | 1 + .../getting-started/what-is-iceoryx.md | 1 + doc/website/index.md | 50 ++---------------- doc/website/stylesheets/TSTARPRO-Bold.otf | Bin 0 -> 42644 bytes doc/website/stylesheets/TSTARPRO-Headline.otf | Bin 0 -> 33720 bytes doc/website/stylesheets/TSTARPRO-Light.otf | Bin 0 -> 43448 bytes doc/website/stylesheets/TSTARPRO-Medium.otf | Bin 0 -> 42740 bytes doc/website/stylesheets/TSTARPRO-Regular.otf | Bin 0 -> 42388 bytes doc/website/stylesheets/extra.css | 13 +++++ mkdocs.yml | 4 ++ 11 files changed, 25 insertions(+), 46 deletions(-) create mode 100644 doc/website/getting-started/what-is-iceoryx.md create mode 100644 doc/website/stylesheets/TSTARPRO-Bold.otf create mode 100644 doc/website/stylesheets/TSTARPRO-Headline.otf create mode 100644 doc/website/stylesheets/TSTARPRO-Light.otf create mode 100644 doc/website/stylesheets/TSTARPRO-Medium.otf create mode 100644 doc/website/stylesheets/TSTARPRO-Regular.otf create mode 100644 doc/website/stylesheets/extra.css diff --git a/README.md b/README.md index 70829bcf6e0..77ec6df2558 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # iceoryx - true zero-copy inter-process-communication

- +

[![Build & Test](https://github.com/eclipse-iceoryx/iceoryx/workflows/Build%20&%20Test/badge.svg?branch=master)](https://github.com/eclipse-iceoryx/iceoryx/actions) diff --git a/doc/website/getting-started/.pages b/doc/website/getting-started/.pages index 5ffe42839d7..f0a02c754b1 100644 --- a/doc/website/getting-started/.pages +++ b/doc/website/getting-started/.pages @@ -1,4 +1,5 @@ nav: - overview.md + - what-is-iceoryx.md - installation.md - examples diff --git a/doc/website/getting-started/what-is-iceoryx.md b/doc/website/getting-started/what-is-iceoryx.md new file mode 100644 index 00000000000..494fb89eb22 --- /dev/null +++ b/doc/website/getting-started/what-is-iceoryx.md @@ -0,0 +1 @@ +# What is Eclipse iceoryx? diff --git a/doc/website/index.md b/doc/website/index.md index fbc5cc80cf5..cb3c3b9647f 100644 --- a/doc/website/index.md +++ b/doc/website/index.md @@ -4,53 +4,13 @@ title: Home # Welcome to iceoryx.io

-

Eclipse iceoryx is a true zero-copy inter-process communication that allows virtually limitless data transfer at constant time. -On the follow pages you find everything you need to get started using Eclipse iceoryx: - -* [Overview and key concepts](getting-started/overview.md) -* [Installation](getting-started/installation.md) -* [Basic examples](getting-started/basic-examples.md) - - - - - - - - - - - - - -
EasySafeFast
-
    -
  • Simple publish-subscribe API with service discovery
  • -
  • Straightforward usage for ROS2 and Adaptive AUTOSAR
  • -
  • Runs on QNX, Linux, MacOS and Windows
  • -
    -Learn more -
-
-
    -
  • Automotive-grade (ISO26262 certification ongoing)
  • -
  • Lock-free algorithms prevent deadlocks
  • -
  • Huge library with safe STL implementations
  • -
    -Learn more -
-
-
    -
  • Written in C++14
  • -
  • C binding available
  • -
  • Zero-copy data transmission with shared memory
  • -
  • Ultra low latency
  • -
    -Learn more -
-
+| **Easy** | **Safe** | **Fast** | +|----|----|-----| +|
  • Simple publish-subscribe API with service discovery
  • Straightforward usage for ROS2 and Adaptive AUTOSAR
  • Runs on QNX, Linux, MacOS and Windows
|
  • Automotive-grade (ISO26262 certification ongoing)
  • Lock-free algorithms prevent deadlocks
  • Huge library with safe STL implementations
|
  • Written in C++14
  • C binding available
  • Zero-copy data transmission with shared memory
  • Ultra low latency
| +|[Learn more :octicons-stopwatch-16:](getting-started/what-is-iceoryx.md#easy){ .md-button }|[Learn more :material-shield-half-full:](getting-started/what-is-iceoryx.md#safe){ .md-button }|[Learn more :material-truck-fast:](getting-started/what-is-iceoryx.md#fast){ .md-button }| diff --git a/doc/website/stylesheets/TSTARPRO-Bold.otf b/doc/website/stylesheets/TSTARPRO-Bold.otf new file mode 100644 index 0000000000000000000000000000000000000000..314ebf1f0872c5abf6fbfc80b70456afedc0a53f GIT binary patch literal 42644 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%0Dqf@NILfcQ!M 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 literal 0 HcmV?d00001 diff --git a/doc/website/stylesheets/TSTARPRO-Headline.otf b/doc/website/stylesheets/TSTARPRO-Headline.otf new file mode 100644 index 0000000000000000000000000000000000000000..4c784ef363a06ba9401ab2990affe2ff4ab39de4 GIT binary patch literal 33720 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%0AK|ATebsj2 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 z%0D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 LJ$PXV9ol~bB6QA2 literal 0 HcmV?d00001 diff --git a/doc/website/stylesheets/TSTARPRO-Regular.otf b/doc/website/stylesheets/TSTARPRO-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..4e61b84d8e95d0455aa0a83911e11eac85cb9a59 GIT binary patch literal 42388 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%0A7WVSm)Vz<$d9 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 Date: Mon, 12 Apr 2021 12:18:04 +0200 Subject: [PATCH 072/127] iox-#482 Apply custom fonts Signed-off-by: Simon Hoinkis --- .../fonts/Monotype-SlatePro-Medium.otf | Bin 0 -> 80944 bytes doc/website/fonts/Monotype-SlatePro.otf | Bin 0 -> 79000 bytes .../{stylesheets => fonts}/TSTARPRO-Bold.otf | Bin .../TSTARPRO-Headline.otf | Bin .../{stylesheets => fonts}/TSTARPRO-Light.otf | Bin .../TSTARPRO-Medium.otf | Bin .../TSTARPRO-Regular.otf | Bin doc/website/stylesheets/extra.css | 59 ++++++++++++++++-- mkdocs.yml | 3 +- 9 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 doc/website/fonts/Monotype-SlatePro-Medium.otf create mode 100644 doc/website/fonts/Monotype-SlatePro.otf rename doc/website/{stylesheets => fonts}/TSTARPRO-Bold.otf (100%) rename doc/website/{stylesheets => fonts}/TSTARPRO-Headline.otf (100%) rename doc/website/{stylesheets => fonts}/TSTARPRO-Light.otf (100%) rename doc/website/{stylesheets => fonts}/TSTARPRO-Medium.otf (100%) rename doc/website/{stylesheets => fonts}/TSTARPRO-Regular.otf (100%) diff --git a/doc/website/fonts/Monotype-SlatePro-Medium.otf b/doc/website/fonts/Monotype-SlatePro-Medium.otf new file mode 100644 index 0000000000000000000000000000000000000000..067b5cb5cbc05045586a4b0a84595c6d132a62a6 GIT binary patch literal 80944 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 literal 0 HcmV?d00001 diff --git a/doc/website/fonts/Monotype-SlatePro.otf b/doc/website/fonts/Monotype-SlatePro.otf new file mode 100644 index 0000000000000000000000000000000000000000..a307b84c5cd0f8bcee2d8fde47e5eb693accf530 GIT binary patch literal 79000 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 xh6hj6pIxyxEAh&v>kU+~ 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 literal 0 HcmV?d00001 diff --git a/doc/website/stylesheets/TSTARPRO-Bold.otf b/doc/website/fonts/TSTARPRO-Bold.otf similarity index 100% rename from doc/website/stylesheets/TSTARPRO-Bold.otf rename to doc/website/fonts/TSTARPRO-Bold.otf diff --git a/doc/website/stylesheets/TSTARPRO-Headline.otf b/doc/website/fonts/TSTARPRO-Headline.otf similarity index 100% rename from doc/website/stylesheets/TSTARPRO-Headline.otf rename to doc/website/fonts/TSTARPRO-Headline.otf diff --git a/doc/website/stylesheets/TSTARPRO-Light.otf b/doc/website/fonts/TSTARPRO-Light.otf similarity index 100% rename from doc/website/stylesheets/TSTARPRO-Light.otf rename to doc/website/fonts/TSTARPRO-Light.otf diff --git a/doc/website/stylesheets/TSTARPRO-Medium.otf b/doc/website/fonts/TSTARPRO-Medium.otf similarity index 100% rename from doc/website/stylesheets/TSTARPRO-Medium.otf rename to doc/website/fonts/TSTARPRO-Medium.otf diff --git a/doc/website/stylesheets/TSTARPRO-Regular.otf b/doc/website/fonts/TSTARPRO-Regular.otf similarity index 100% rename from doc/website/stylesheets/TSTARPRO-Regular.otf rename to doc/website/fonts/TSTARPRO-Regular.otf diff --git a/doc/website/stylesheets/extra.css b/doc/website/stylesheets/extra.css index b0bff55c965..fffc48d1bcc 100644 --- a/doc/website/stylesheets/extra.css +++ b/doc/website/stylesheets/extra.css @@ -1,13 +1,62 @@ @font-face { - font-family: "TStar Pro"; - src: url("./TSTARPRO-Regular.otf") format("opentype"); - } + font-family: "TStar Pro Regular"; + src: url("../fonts/TSTARPRO-Regular.otf") format("opentype"); +} + +@font-face { + font-family: "TStar Pro Bold"; + src: url("../fonts/TSTARPRO-Bold.otf") format("opentype"); +} + +@font-face { + font-family: "TStar Pro Headline"; + src: url("../fonts/TSTARPRO-Headline.otf") format("opentype"); +} + +@font-face { + font-family: "TStar Pro Light"; + src: url("../fonts/TSTARPRO-Light.otf") format("opentype"); +} + +@font-face { + font-family: "TStar Pro Medium"; + src: url("../fonts/TSTARPRO-Medium.otf") format("opentype"); +} + +@font-face { + font-family: "Slate Pro"; + src: url("../fonts/Monotype-SlatePro.otf") format("opentype"); +} + +@font-face { + font-family: "Slate Pro Medium"; + src: url("../fonts/Monotype-SlatePro-Medium.otf") format("opentype"); +} :root { --md-primary-fg-color: #2f8db3; --md-primary-fg-color--light: #FFFFFF; --md-primary-fg-color--dark: #8dc6d9; --md-accent-fg-color: #ff4d00; - --md-text-font-family: "TStar Pro"; - } +} + +body +{ + font-family: "TStar Pro Regular", sans-serif; +} + +h1 +{ + font-family: "Slate Pro", sans-serif; + text-transform: uppercase; +} + +h2 +{ + font-family: "TStar Pro Bold", sans-serif; +} +strong +{ + font-family: "TStar Pro Bold", sans-serif; +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 115b5d61ce5..12ce4f8082f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,4 +28,5 @@ extra: version: provider: mike extra_css: - - stylesheets/extra.css \ No newline at end of file + - stylesheets/extra.css + \ No newline at end of file From 7049009f94fac54d334857d4b01a24b7266ca591 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Mon, 12 Apr 2021 12:31:15 +0200 Subject: [PATCH 073/127] iox-#482 Remove index files after doxybook2 generation Signed-off-by: Simon Hoinkis --- tools/export-docu-to-website.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/export-docu-to-website.sh b/tools/export-docu-to-website.sh index 9f013fc1817..e46d34897b4 100755 --- a/tools/export-docu-to-website.sh +++ b/tools/export-docu-to-website.sh @@ -57,6 +57,16 @@ doxybook2 --input $WORKSPACE/build/doc/iceoryx_dds/xml/ --output $WORKSPACE/doc/ mkdir -p $WORKSPACE/doc/website/API-reference/introspection doxybook2 --input $WORKSPACE/build/doc/iceoryx_introspection/xml/ --output $WORKSPACE/doc/website/API-reference/introspection +# Remove index files +PACKAGES="utils posh c-binding DDS-gateway introspection" +FILES="index_classes.md index_examples.md index_files.md index_modules.md index_namespaces.md index_pages.md" + +for PACKAGE in ${PACKAGES} ; do + for FILE in ${FILES} ; do + rm $WORKSPACE/doc/website/API-reference/$PACKAGE/$FILE + done +done + if [ "$TYPE" == "local" ]; then echo "starting local webserver" From 4a07c6e21a22881990e82288d6cdd38b739ef090 Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Mon, 12 Apr 2021 14:31:38 +0200 Subject: [PATCH 074/127] iox-#482 Add icons to sections for website Signed-off-by: Simon Hoinkis --- doc/website/advanced/configuration-guide.md | 6 +++--- .../installation-guide-for-contributors.md | 6 +++--- doc/website/getting-started/installation.md | 14 +++++++------- doc/website/index.md | 4 ++-- iceoryx_examples/iceensemble/README.md | 4 ++++ iceoryx_utils/README.md | 9 ++++++++- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/doc/website/advanced/configuration-guide.md b/doc/website/advanced/configuration-guide.md index d1678631339..53af8981556 100644 --- a/doc/website/advanced/configuration-guide.md +++ b/doc/website/advanced/configuration-guide.md @@ -1,6 +1,6 @@ # Configuration guide -## CMake switches for configuring iceoryx_posh build +## :material-cog: CMake switches for configuring iceoryx_posh build When building iceoryx_posh, there are several configuration options set by default. These options adjust the limits of Publisher and Subscriber Ports for resource management. These limits are used to create management structures in the shared memory segment called `iceoryx_mgmt` when starting up RouDi. @@ -28,7 +28,7 @@ cmake -Bbuild -Hiceoryx_meta -DIOX_MAX_CHUNKS_HELD_PER_SUBSCRIBER_SIMULTANEOUSLY With that change, the footprint of the management segment is reduced to ~52.7 MBytes. For larger use cases you can increase the value to avoid that samples are dropped on the subscriber side (see also [#615](https://github.com/eclipse-iceoryx/iceoryx/issues/615)). -## Configuring Mempools for RouDi +## :material-memory: Configuring Mempools for RouDi RouDi supports several shared memory segments with different access rights, to limit the read and write access between different applications. Inside of these segments reside mempools where the user payload data for transfer is stored. Based on the [conceptual guide](https://github.com/eclipse-iceoryx/iceoryx/blob/master/doc/conceptual-guide.md) the end-user may want to configure the mempools with the number of chunks and their size. @@ -43,7 +43,7 @@ For building RouDi, iceoryx ships a library named `iceoryx_posh_roudi`. This lib The value for the alignment is set to 32. -### Dynamic configuration +### :material-file-cog: Dynamic configuration One way is to read a configuration dynamically at RouDi runtime (startup). Using TOML Config in RouDi is not mandatory for configuring segments and mempools, but a comfortable alternative. diff --git a/doc/website/advanced/installation-guide-for-contributors.md b/doc/website/advanced/installation-guide-for-contributors.md index 0b8dbcdb62a..9f7dfe85fd0 100644 --- a/doc/website/advanced/installation-guide-for-contributors.md +++ b/doc/website/advanced/installation-guide-for-contributors.md @@ -1,6 +1,6 @@ # Installation guide for contributors -## Build and run tests +## :material-test-tube: Build and run tests While developing on iceoryx you want to know if your changes are breaking existing functions or if your newly written tests are passing. For that purpose, we are generating CMake targets that are executing the tests. First, we need to build them: @@ -44,7 +44,7 @@ Let's assume you want to execute only `ServiceDescription_test` from posh_module ./build/posh/test/posh_moduletests --gtest_filter="ServiceDescription_test*" ``` -## Use Sanitizer Scan +## :fontawesome-solid-pump-soap: Use Sanitizer Scan Due to the fact that iceoryx works a lot with system memory, it should be ensured that errors like memory leaks are not introduced. To prevent this, we use the clang toolchain which offers several tools for scanning the codebase. One of them is the [Address-Sanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) which checks for example on dangling pointers. @@ -83,7 +83,7 @@ This should be only rarely used and only in coordination with an iceoryx maintai iceoryx needs to be built as a static library for working with sanitizer flags. The script does it automatically. Except when you want to use the ${ICEORYX_WARNINGS} then you have to call `findpackage(iceoryx_utils)` -## iceoryx library build +## :material-library: iceoryx library build The iceoryx build consists of several libraries which have dependencies on each other. The goal is to have self-encapsulated library packages available where the end-user can easily find it with the CMake command `find-package(...)`. In the default case, the iceoryx libraries are installed by `make install` into `/usr/lib` which requires root access. As an alternative you can install the libs into a custom folder by setting `-DCMAKE_INSTALL_PREFIX=/custom/install/path` as build-flag for the CMake file in iceoryx_meta. diff --git a/doc/website/getting-started/installation.md b/doc/website/getting-started/installation.md index 2622424ce06..9a819a7ed58 100644 --- a/doc/website/getting-started/installation.md +++ b/doc/website/getting-started/installation.md @@ -4,7 +4,7 @@ All iceoryx libraries are deployed as independent CMake packages. Posh is using ## Prerequisites -### Dependencies +### :octicons-package-dependencies-16: Dependencies - 64-bit hardware (e.g. x86_64 or aarch64; 32-bit hardware might work, but is not supported) - [CMake](https://cmake.org), 3.10 or later @@ -27,7 +27,7 @@ Furthermore, you have to install: !!! hint If you are behind a corporate firewall you may have to adjust the proxy settings of maven in `/etc/maven/settings.xml`. See: [Maven Proxy Configuration](https://maven.apache.org/settings.html#proxies) -### Mac OS +### :material-apple: Mac OS Before installing iceoryx you need to install XCode and git. Optionally, ncurses library is required for the introspection client. To install ncurses locally into your build folder follow these steps @@ -45,7 +45,7 @@ make -j12 make install ``` -### Linux +### :fontawesome-brands-linux: Linux Although we strive to be fully POSIX-compliant, we recommend using Ubuntu 18.04 and at least GCC 7.5.0 for development. @@ -57,7 +57,7 @@ sudo apt install gcc g++ cmake libacl1-dev libncurses5-dev pkg-config Additionally, there is an optional dependency to the [cpptoml](https://github.com/skystrife/cpptoml) library, which is used to parse the RouDi config file containing mempool configuration. -### QNX +### :fontawesome-brands-blackberry: QNX QNX SDP 7.0 and 7.1 are supported (shipping with gcc 5.4 and gcc 8.3 respectively). @@ -79,7 +79,7 @@ X86_64: !!! attention Please ensure that the folder `/var/lock` exist and the filesystem supports file locking. -## Build with CMake +## :material-triangle: Build with CMake !!! note Building with CMake is the preferred way, for more complex actions like a coverage scan @@ -141,7 +141,7 @@ The `CMakeLists.txt` from `iceoryx_meta` can be used to easily develop iceoryx w Please take a look at the CMake file [build_options.cmake](https://github.com/eclipse-iceoryx/iceoryx/blob/master/iceoryx_meta/build_options.cmake) to get an overview of the available build options for enabling additional features. -## Build with script +## :material-powershell: Build with script As an alternative, we provide a build-test script which we use to integrate iceoryx into our infrastructure. The intention of the script goes beyond building iceoryx, it is also used for the code coverage scan or the address-sanitizer runs on the CI. @@ -172,7 +172,7 @@ You can use the `help` argument for getting an overview of the available options !!! tip The examples can be built with `-DEXAMPLES=ON` with iceoryx_meta or by providing the `examples` argument to the build script. -## Build with colcon +## :material-robot: Build with colcon Alternatively, iceoryx can be built with [colcon](https://colcon.readthedocs.io/en/released/user/installation.html#using-debian-packages) to provide a smooth integration for ROS2 developers. To build the iceoryx_integrationtest package one requires a minimal [ROS2 installation](https://docs.ros.org/en/foxy/Installation/Linux-Install-Debians.html). diff --git a/doc/website/index.md b/doc/website/index.md index cb3c3b9647f..8ecb158d9d1 100644 --- a/doc/website/index.md +++ b/doc/website/index.md @@ -10,7 +10,7 @@ title: Home Eclipse iceoryx is a true zero-copy inter-process communication that allows virtually limitless data transfer at constant time. -| **Easy** | **Safe** | **Fast** | +|

:octicons-stopwatch-16: Easy

|

:material-shield-half-full: Safe

|

:material-truck-fast: Fast

| |----|----|-----| |
  • Simple publish-subscribe API with service discovery
  • Straightforward usage for ROS2 and Adaptive AUTOSAR
  • Runs on QNX, Linux, MacOS and Windows
|
  • Automotive-grade (ISO26262 certification ongoing)
  • Lock-free algorithms prevent deadlocks
  • Huge library with safe STL implementations
|
  • Written in C++14
  • C binding available
  • Zero-copy data transmission with shared memory
  • Ultra low latency
| -|[Learn more :octicons-stopwatch-16:](getting-started/what-is-iceoryx.md#easy){ .md-button }|[Learn more :material-shield-half-full:](getting-started/what-is-iceoryx.md#safe){ .md-button }|[Learn more :material-truck-fast:](getting-started/what-is-iceoryx.md#fast){ .md-button }| +|[Learn more](getting-started/what-is-iceoryx.md#easy){ .md-button }|[Learn more ](getting-started/what-is-iceoryx.md#safe){ .md-button }|[Learn more](getting-started/what-is-iceoryx.md#fast){ .md-button }| diff --git a/iceoryx_examples/iceensemble/README.md b/iceoryx_examples/iceensemble/README.md index 26171fb0c88..0a93b5b9608 100644 --- a/iceoryx_examples/iceensemble/README.md +++ b/iceoryx_examples/iceensemble/README.md @@ -38,3 +38,7 @@ Alternatively, you can use the provided [tmux](https://en.wikipedia.org/wiki/Tmu ## Expected Output + +
+[Check out iceensemble on GitHub :fontawesome-brands-github:](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_examples/iceensemble){ .md-button } +
diff --git a/iceoryx_utils/README.md b/iceoryx_utils/README.md index 2bea5116d0c..ef3d265b99a 100644 --- a/iceoryx_utils/README.md +++ b/iceoryx_utils/README.md @@ -105,6 +105,7 @@ attribute overview of the available Queues: ### Error handling The Error-Handler is a central instance for collecting al errors and react to them. In the file `error-handling.hpp` are all error enums collected. The Error-Handler has different error-levels, for more information see [error-handling.md](../doc/design/error-handling.md) + | class | internal | maybe obsolete | description | |:-----------------------:|:--------:|:--------------:|:------------| |`errorHandler` | | | Free function to call the Error-Handler with a defined error and an error-level, see header file for practical example.| @@ -113,6 +114,7 @@ The Error-Handler is a central instance for collecting al errors and react to th ### Log For information about how to use the logger API see [error-handling.md](../doc/design/error-handling.md) + | class | internal | maybe obsolete | description | |:-----------------------:|:--------:|:--------------:|:------------| |`logger` | | | | @@ -144,6 +146,7 @@ abstractions or add a new one when using POSIX resources like semaphores, shared Never use physical properties like speed or time directly as integer or float in your code. Otherwise you encounter problems like this function `void setTimeout(int timeout)`. What is the unit of the argument, seconds? minutes? If you use our `Duration` you see it directly in the code. + ``` void setTimeout(const Duration & timeout); @@ -172,4 +175,8 @@ setTimeout(5_ms); // 5 milliseconds | class | internal | maybe obsolete | description | |:-----------------------:|:--------:|:--------------:|:------------| -|`FileReader` | i | x | Wrapper for opening files and reading them. Maybe not needed. | \ No newline at end of file +|`FileReader` | i | x | Wrapper for opening files and reading them. Maybe not needed. | + +
+[Check out iceoryx_utils on GitHub :fontawesome-brands-github:](https://github.com/eclipse-iceoryx/iceoryx/tree/master/iceoryx_utils/){ .md-button } +
From 9f2f0361559860d95cce2d5885c1538e4e52062b Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Mon, 12 Apr 2021 18:08:17 +0200 Subject: [PATCH 075/127] iox-#482 Add and style custom footer for website Signed-off-by: Simon Hoinkis --- doc/website/images/apex-logo.webp | Bin 0 -> 3410 bytes doc/website/images/bosch-logo.png | Bin 0 -> 41625 bytes doc/website/images/eclipse-logo.svg | 15 ++++ doc/website/images/iceoryx-logo-white.png | Bin 0 -> 10206 bytes doc/website/overrides/partials/footer.html | 96 +++++++++++++++++++++ doc/website/stylesheets/extra.css | 28 ++++-- mkdocs.yml | 3 +- 7 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 doc/website/images/apex-logo.webp create mode 100644 doc/website/images/bosch-logo.png create mode 100644 doc/website/images/eclipse-logo.svg create mode 100644 doc/website/images/iceoryx-logo-white.png create mode 100644 doc/website/overrides/partials/footer.html diff --git a/doc/website/images/apex-logo.webp b/doc/website/images/apex-logo.webp new file mode 100644 index 0000000000000000000000000000000000000000..03a07f5bdc150fe025137e2733d7676c1f52abfb GIT binary patch literal 3410 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 literal 0 HcmV?d00001 diff --git a/doc/website/images/bosch-logo.png b/doc/website/images/bosch-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c664d6e9479dd346597ffbcd187ba47503d416a4 GIT binary patch literal 41625 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 literal 0 HcmV?d00001 diff --git a/doc/website/images/eclipse-logo.svg b/doc/website/images/eclipse-logo.svg new file mode 100644 index 00000000000..78226c71f81 --- /dev/null +++ b/doc/website/images/eclipse-logo.svg @@ -0,0 +1,15 @@ + + + + eclipse_foundation_logo + Created with Sketch. + + + + \ No newline at end of file diff --git a/doc/website/images/iceoryx-logo-white.png b/doc/website/images/iceoryx-logo-white.png new file mode 100644 index 0000000000000000000000000000000000000000..04c00e8187028c2ec917d59f609406d4d57ff63f GIT binary patch literal 10206 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 a-Vx5QzxBO);TZSNucoA}SRrp6@&5okmaymm literal 0 HcmV?d00001 diff --git a/doc/website/overrides/partials/footer.html b/doc/website/overrides/partials/footer.html new file mode 100644 index 00000000000..21105d0007c --- /dev/null +++ b/doc/website/overrides/partials/footer.html @@ -0,0 +1,96 @@ + \ No newline at end of file diff --git a/doc/website/stylesheets/extra.css b/doc/website/stylesheets/extra.css index fffc48d1bcc..6830298c1e9 100644 --- a/doc/website/stylesheets/extra.css +++ b/doc/website/stylesheets/extra.css @@ -40,23 +40,33 @@ --md-accent-fg-color: #ff4d00; } -body -{ +body { font-family: "TStar Pro Regular", sans-serif; } -h1 -{ +h1 { font-family: "Slate Pro", sans-serif; text-transform: uppercase; } -h2 -{ +h2 { font-family: "TStar Pro Bold", sans-serif; } -strong -{ +strong { font-family: "TStar Pro Bold", sans-serif; -} \ No newline at end of file +} + +.md-footer-meta { + height: 200px; + display: flex; +} + +.md-footer-meta__inner { + flex: 0 0 25%; + justify-content: left; +} + +.md-footer-copyright { + margin: 0 .6rem; +} diff --git a/mkdocs.yml b/mkdocs.yml index 12ce4f8082f..fe9ada56fa1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -22,8 +22,9 @@ plugins: - search theme: name: 'material' + custom_dir: doc/website/overrides font: false - logo: https://user-images.githubusercontent.com/8661268/114322343-016b4c80-9b20-11eb-853b-bff3739da457.png + logo: images/iceoryx-logo-white.png extra: version: provider: mike From 0055a6ef0471128d3aad5d5ffdad33e629c9d02e Mon Sep 17 00:00:00 2001 From: Simon Hoinkis Date: Mon, 12 Apr 2021 20:38:45 +0200 Subject: [PATCH 076/127] iox-#482 Make footer responsive and swap overview with what is Signed-off-by: Simon Hoinkis --- doc/website/getting-started/.pages | 2 +- doc/website/overrides/partials/footer.html | 5 +++-- doc/website/stylesheets/extra.css | 11 +++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/doc/website/getting-started/.pages b/doc/website/getting-started/.pages index f0a02c754b1..6c26ce6be82 100644 --- a/doc/website/getting-started/.pages +++ b/doc/website/getting-started/.pages @@ -1,5 +1,5 @@ nav: - - overview.md - what-is-iceoryx.md + - overview.md - installation.md - examples diff --git a/doc/website/overrides/partials/footer.html b/doc/website/overrides/partials/footer.html index 21105d0007c..1ff40a7e38c 100644 --- a/doc/website/overrides/partials/footer.html +++ b/doc/website/overrides/partials/footer.html @@ -52,7 +52,8 @@ Terms of use
Copyright
Legal
- Report a safety or security issue + Report a safety or security + vulnerability