diff --git a/Android.bp b/Android.bp index aae86e43b..8548f44ff 100644 --- a/Android.bp +++ b/Android.bp @@ -74,7 +74,7 @@ cc_library_shared { cflags: [ "-DWITHOUT_SYSTEMD", - "-DVSOMEIP_VERSION=\"3.6.0\"", + "-DVSOMEIP_VERSION=\"3.6.1\"", "-DVSOMEIP_BASE_PATH=\"/vendor/run/someip/\"", ], diff --git a/Android.mk b/Android.mk index 5162f112c..0e8f9d368 100644 --- a/Android.mk +++ b/Android.mk @@ -101,7 +101,7 @@ LOCAL_CFLAGS := \ -frtti \ -fexceptions \ -DWITHOUT_SYSTEMD \ - -DVSOMEIP_VERSION=\"3.6.0\" \ + -DVSOMEIP_VERSION=\"3.6.1\" \ -DVSOMEIP_BASE_PATH=\"/vendor/run/someip/\" \ -Wno-unused-parameter \ -Wno-non-virtual-dtor \ @@ -160,7 +160,7 @@ LOCAL_CFLAGS := \ -frtti \ -fexceptions \ -DWITHOUT_SYSTEMD \ - -DVSOMEIP_VERSION=\"3.6.0\" \ + -DVSOMEIP_VERSION=\"3.6.1\" \ -DVSOMEIP_BASE_PATH=\"/vendor/run/someip/\" \ -Wno-unused-parameter \ -Wno-non-virtual-dtor \ diff --git a/CHANGES b/CHANGES index 956df7a1d..79b51167c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,38 @@ Changes ======= +v3.6.1 +- global + - support compilation with gcc15 + - enable CMAKE_EXPORT_COMPILE_COMMANDS + - introduce a thread-safe timer utility + +- application + - fail early, fail fast for unknown messages + - revert "IO threads exit mitigation" + +- routing + - create local receiver on start + - fix remote subscription expiration + - deliver service availability only after endpoint creation + +- tests + - add regression test - client ignores server connections for a while + - add another fake_socket test + - update subscribe notify tests documentation + - update shebang line to interpret as bash + - new network-test for start-stop-start application + - doc and fix second address tests + - doc and fix offer_test + - doc and fix routing tests + - fix payload tests return codes + +- endpoints + - fix race condition in connections_ map changes + - drop unused state + - no waiting when stopping due to error + - set tcp quick ack + v3.6.0 - global - drop vsomeip2 compatibility diff --git a/CMakeLists.txt b/CMakeLists.txt index a0d455fb9..6eea26ee6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ set (VSOMEIP_NAME vsomeip3) set (VSOMEIP_MAJOR_VERSION 3) set (VSOMEIP_MINOR_VERSION 6) -set (VSOMEIP_PATCH_VERSION 0) +set (VSOMEIP_PATCH_VERSION 1) set (VSOMEIP_HOTFIX_VERSION 0) set (VSOMEIP_VERSION ${VSOMEIP_MAJOR_VERSION}.${VSOMEIP_MINOR_VERSION}.${VSOMEIP_PATCH_VERSION}) @@ -54,6 +54,9 @@ foreach (p LIB BIN INCLUDE CMAKE) endforeach () ################################################################################################### + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + # Set a default build type if none was specified set(default_build_type "RelWithDebInfo") if(NOT CMAKE_BUILD_TYPE) @@ -63,7 +66,10 @@ if(NOT CMAKE_BUILD_TYPE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() -set(CMAKE_CXX_STANDARD 17) +if(NOT DEFINED VSOMEIP_CXX_STANDARD) + set (VSOMEIP_CXX_STANDARD 17) +endif() +set(CMAKE_CXX_STANDARD ${VSOMEIP_CXX_STANDARD}) # OS if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") @@ -149,11 +155,11 @@ if (NOT MSVC) endif () if (VALGRIND_TYPE STREQUAL "massif") - set(TEST_ENTRYPOINT ${TEST_ENTRYPOINT} --massif-out-file=${VALGRIND_LOGS_DIR}/test_name.out) + set(TEST_ENTRYPOINT ${TEST_ENTRYPOINT} --massif-out-file=${VALGRIND_LOGS_DIR}/test_name.%p.out) endif () if (VALGRIND_TYPE STREQUAL "memcheck") - set(TEST_ENTRYPOINT ${TEST_ENTRYPOINT} --leak-check=yes --suppressions=${VALGRIND_SUPPRESS_FILE} --log-file=${VALGRIND_LOGS_DIR}/test_name.out) + set(TEST_ENTRYPOINT ${TEST_ENTRYPOINT} --leak-check=yes --suppressions=${VALGRIND_SUPPRESS_FILE} --log-file=${VALGRIND_LOGS_DIR}/test_name.%p.out) endif () endif () endif (NOT MSVC) @@ -291,7 +297,7 @@ list(SORT ${VSOMEIP_NAME}-cfg_SRC) if (VSOMEIP_ENABLE_MULTIPLE_ROUTING_MANAGERS EQUAL 0) add_library(${VSOMEIP_NAME}-cfg SHARED ${${VSOMEIP_NAME}-cfg_SRC}) set_target_properties (${VSOMEIP_NAME}-cfg PROPERTIES VERSION ${VSOMEIP_VERSION} SOVERSION ${VSOMEIP_MAJOR_VERSION}) - target_compile_features(${VSOMEIP_NAME}-cfg PRIVATE cxx_std_17) + target_compile_features(${VSOMEIP_NAME}-cfg PRIVATE cxx_std_${VSOMEIP_CXX_STANDARD}) if (MSVC) set_target_properties(${VSOMEIP_NAME}-cfg PROPERTIES COMPILE_DEFINITIONS "VSOMEIP_DLL_COMPILATION_PLUGIN") endif() @@ -326,7 +332,7 @@ list(SORT ${VSOMEIP_NAME}_SRC) add_library(${VSOMEIP_NAME} SHARED ${${VSOMEIP_NAME}_SRC}) set_target_properties (${VSOMEIP_NAME} PROPERTIES VERSION ${VSOMEIP_VERSION} SOVERSION ${VSOMEIP_MAJOR_VERSION}) -target_compile_features(${VSOMEIP_NAME} PRIVATE cxx_std_17) +target_compile_features(${VSOMEIP_NAME} PRIVATE cxx_std_${VSOMEIP_CXX_STANDARD}) if (MSVC) set_target_properties(${VSOMEIP_NAME} PROPERTIES COMPILE_DEFINITIONS "VSOMEIP_DLL_COMPILATION") set_target_properties(${VSOMEIP_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) @@ -363,7 +369,7 @@ file(GLOB ${VSOMEIP_NAME}-sd_SRC list(SORT ${VSOMEIP_NAME}-sd_SRC) add_library(${VSOMEIP_NAME}-sd SHARED ${${VSOMEIP_NAME}-sd_SRC}) -target_compile_features(${VSOMEIP_NAME}-sd PRIVATE cxx_std_17) +target_compile_features(${VSOMEIP_NAME}-sd PRIVATE cxx_std_${VSOMEIP_CXX_STANDARD}) set_target_properties (${VSOMEIP_NAME}-sd PROPERTIES VERSION ${VSOMEIP_VERSION} SOVERSION ${VSOMEIP_MAJOR_VERSION}) if (MSVC) set_target_properties(${VSOMEIP_NAME}-sd PROPERTIES COMPILE_DEFINITIONS "VSOMEIP_DLL_COMPILATION_PLUGIN") @@ -381,7 +387,7 @@ file(GLOB_RECURSE ${VSOMEIP_NAME}-e2e_SRC list(SORT ${VSOMEIP_NAME}-e2e_SRC) add_library(${VSOMEIP_NAME}-e2e SHARED ${${VSOMEIP_NAME}-e2e_SRC}) -target_compile_features(${VSOMEIP_NAME}-e2e PRIVATE cxx_std_17) +target_compile_features(${VSOMEIP_NAME}-e2e PRIVATE cxx_std_${VSOMEIP_CXX_STANDARD}) set_target_properties (${VSOMEIP_NAME}-e2e PROPERTIES VERSION ${VSOMEIP_VERSION} SOVERSION ${VSOMEIP_MAJOR_VERSION}) if (MSVC) set_target_properties(${VSOMEIP_NAME}-e2e PROPERTIES COMPILE_DEFINITIONS "VSOMEIP_DLL_COMPILATION_PLUGIN") diff --git a/build_qnx/common.mk b/build_qnx/common.mk index c7905f681..2b674fcd7 100644 --- a/build_qnx/common.mk +++ b/build_qnx/common.mk @@ -44,7 +44,7 @@ EXTRA_LIBVPATH = $(USE_ROOT_LIB)/io-sock CMAKE_ARGS = -DCMAKE_TOOLCHAIN_FILE=$(PROJECT_ROOT)/qnx.nto.toolchain.cmake \ -DCMAKE_INSTALL_PREFIX=$(VSOMEIP_INSTALL_ROOT)/$(CPUVARDIR)/usr \ - -DCMAKE_CXX_STANDARD=17 \ + -DCMAKE_CXX_STANDARD=$(VSOMEIP_CXX_STANDARD) \ -DCMAKE_NO_SYSTEM_FROM_IMPORTED=TRUE \ -DVSOMEIP_EXTERNAL_DEPS_INSTALL=$(VSOMEIP_EXTERNAL_DEPS_INSTALL) \ -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \ diff --git a/build_qnx/qnx.nto.toolchain.cmake b/build_qnx/qnx.nto.toolchain.cmake index f87ed7218..faa103fb8 100644 --- a/build_qnx/qnx.nto.toolchain.cmake +++ b/build_qnx/qnx.nto.toolchain.cmake @@ -24,6 +24,10 @@ set(CMAKE_CXX_COMPILER ${QNX_HOST}/usr/bin/qcc) set(CMAKE_AR "${QNX_HOST}/usr/bin/nto${CMAKE_SYSTEM_PROCESSOR}-ar${HOST_EXECUTABLE_SUFFIX}" CACHE PATH "archiver") set(CMAKE_RANLIB "${QNX_HOST}/usr/bin/nto${CMAKE_SYSTEM_PROCESSOR}-ranlib${HOST_EXECUTABLE_SUFFIX}" CACHE PATH "ranlib") +if(NOT DEFINED VSOMEIP_CXX_STANDARD) + set (VSOMEIP_CXX_STANDARD 17) +endif() + if ("${GCC_VER}" STREQUAL "") set(GCC_VERSION "" CACHE STRING "gcc_ver") else() diff --git a/documentation/vsomeipConfiguration.md b/documentation/vsomeipConfiguration.md index d0e14d8ad..1ea849e9a 100644 --- a/documentation/vsomeipConfiguration.md +++ b/documentation/vsomeipConfiguration.md @@ -39,9 +39,7 @@ ## Logging - **logging** - Used to configure the log messages of vSomeIP - - **console** - Specifies whether logging via console is enabled, valid values are `true` or `false`. The default value is `true`. - - **file**: - **enable** - Specifies whether a log file should be created, valid values are `true` or `false`. The default value is `false`. - **path** - The absolute path of the log file. The default value is `/tmp/vsomeip.log`. @@ -75,7 +73,6 @@ ## Routing - **routing** (optional) - Specifies the properties of the routing. Either a string that specifies the application that hosts the routing component or a structure that specifies all properties of the routing. If the routing is not specified, the first started application will host the routing component. - - **host** - Properties of the routing manager. - **name** - Name of the application that hosts the routing component. - **uid** - User identifier of the process that runs the routing component. Must be specified if credential checks are enabled by check_credentials set to true. @@ -229,7 +226,6 @@ The following example assumes that different machines (i.e., with different IP a - **additional** - Generic way to define configuration data for plugins. - **debounce** - Client/Application specific configuration of debouncing. - **has_session_handling** - Configures the session handling. Mostly used for E2E use cases when the application handles the CRC calculation over the SOME/IP header by themself, and need the ability to switch off the session handling as otherwise their calculated checksum does not match reality after vsomeip inserts the session identifier. Valid values are `true` or `false`. The default value is `true`. - - **event_loop_periodicity** (optional) - If set to a positive value, it enables the io_context object's event processing run_for implementation to run the loop based on duration instead of running it until the work queue has work to be done. The default value is `0` seconds, which uses the io_context run interface.
Example of Applications configuration diff --git a/examples/hello_world/CMakeLists.txt b/examples/hello_world/CMakeLists.txt index c07a793a2..22b7ae026 100644 --- a/examples/hello_world/CMakeLists.txt +++ b/examples/hello_world/CMakeLists.txt @@ -15,7 +15,7 @@ function(create_target executable) target_sources(vsomeip_hello_world_${executable} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/hello_world_${executable}.hpp" ) - target_compile_features(vsomeip_hello_world_${executable} INTERFACE cxx_std_17) + target_compile_features(vsomeip_hello_world_${executable} INTERFACE cxx_std_${VSOMEIP_CXX_STANDARD}) target_include_directories(vsomeip_hello_world_${executable} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/implementation/configuration/include/application_configuration.hpp b/implementation/configuration/include/application_configuration.hpp index bc1422154..c53591c1c 100644 --- a/implementation/configuration/include/application_configuration.hpp +++ b/implementation/configuration/include/application_configuration.hpp @@ -25,7 +25,6 @@ struct application_configuration { std::size_t max_detach_thread_wait_time_; std::size_t thread_count_; std::size_t request_debounce_time_; - std::size_t event_loop_periodicity_; std::map> plugins_; int nice_level_; debounce_configuration_t debounces_; diff --git a/implementation/configuration/include/configuration.hpp b/implementation/configuration/include/configuration.hpp index bd20eb178..afd2e7939 100644 --- a/implementation/configuration/include/configuration.hpp +++ b/implementation/configuration/include/configuration.hpp @@ -6,17 +6,19 @@ #ifndef VSOMEIP_V3_CONFIGURATION_HPP #define VSOMEIP_V3_CONFIGURATION_HPP +#include +#include #include #include #include #include -#include +#include #include #include -#include #include +#include #include #include #include @@ -42,6 +44,7 @@ class policy_manager_impl; class security; class event; struct debounce_filter_impl_t; +struct port_range_t; class configuration { public: @@ -127,15 +130,6 @@ class configuration { virtual std::size_t get_request_debounce_time(const std::string& _name) const = 0; virtual bool has_session_handling(const std::string& _name) const = 0; - /** - * @brief Get the boost asio context event loop periodicity. - * If set to a value greather than 0, run for is used with the defined period. - * - * @param _name Application name - * @return std::size_t event loop period. - */ - virtual std::size_t get_event_loop_periodicity(const std::string& _name) const = 0; - virtual std::uint32_t get_max_message_size_local() const = 0; virtual std::uint32_t get_max_message_size_reliable(const std::string& _address, std::uint16_t _port) const = 0; virtual std::uint32_t get_max_message_size_unreliable() const = 0; @@ -246,7 +240,6 @@ class configuration { virtual bool is_protected_port(const boost::asio::ip::address& _address, std::uint16_t _port, bool _reliable) const = 0; virtual bool is_secure_port(const boost::asio::ip::address& _address, std::uint16_t _port, bool _reliable) const = 0; - typedef std::pair port_range_t; virtual void set_sd_acceptance_rule(const boost::asio::ip::address& _address, port_range_t _port_range, port_type_e _type, const std::string& _path, bool _reliable, bool _enable, bool _default) = 0; @@ -303,6 +296,29 @@ class configuration { virtual std::shared_ptr get_security() const = 0; }; +/// Inclusive port range. +struct port_range_t { + /// Start of the port range. + std::uint16_t start_{ANY_PORT}; + + /// End (inclusive) of the port range. + std::uint16_t end_{ANY_PORT}; + + /// Creates a new `port_range_t`. + port_range_t(const std::uint16_t _start, const std::uint16_t _end) : start_(_start), end_(_end) { + // Fix swapped values. + if (start_ > end_) { + std::swap(start_, end_); + } + } + + /// Whether the given value is within this port range. + [[nodiscard]] bool contains(const std::uint16_t _value) const { return _value >= start_ && _value <= end_; } + + /// Whether both ends of this range are set to `ANY_PORT`. + [[nodiscard]] bool is_any() const { return start_ == ANY_PORT && end_ == ANY_PORT; } +}; + } // namespace vsomeip_v3 #endif // VSOMEIP_V3_CONFIGURATION_HPP diff --git a/implementation/configuration/include/configuration_impl.hpp b/implementation/configuration/include/configuration_impl.hpp index 50fad391d..27be83346 100644 --- a/implementation/configuration/include/configuration_impl.hpp +++ b/implementation/configuration/include/configuration_impl.hpp @@ -133,7 +133,6 @@ class configuration_impl : public configuration, public std::enable_shared_from_ VSOMEIP_EXPORT int get_io_thread_nice_level(const std::string& _name) const; VSOMEIP_EXPORT std::size_t get_request_debounce_time(const std::string& _name) const; VSOMEIP_EXPORT bool has_session_handling(const std::string& _name) const; - VSOMEIP_EXPORT std::size_t get_event_loop_periodicity(const std::string& _name) const; VSOMEIP_EXPORT std::set> get_remote_services() const; diff --git a/implementation/configuration/include/internal.hpp.in b/implementation/configuration/include/internal.hpp.in index 4ec76141c..c2fa1fb42 100644 --- a/implementation/configuration/include/internal.hpp.in +++ b/implementation/configuration/include/internal.hpp.in @@ -128,8 +128,6 @@ #define VSOMEIP_MINIMUM_CHECK_TTL_TIMEOUT 100 #define VSOMEIP_SETSOCKOPT_TIMEOUT_US 500000 // us -#define VSOMEIP_DEFAULT_EVENT_LOOP_PERIODICITY 0 // seconds - #define LOCAL_TCP_PORT_WAIT_TIME @VSOMEIP_LOCAL_TCP_PORT_WAIT_TIME@ #define LOCAL_TCP_PORT_MAX_WAIT_TIME @VSOMEIP_LOCAL_TCP_PORT_MAX_WAIT_TIME@ diff --git a/implementation/configuration/include/internal_android.hpp b/implementation/configuration/include/internal_android.hpp index 5269779c6..85036e384 100644 --- a/implementation/configuration/include/internal_android.hpp +++ b/implementation/configuration/include/internal_android.hpp @@ -111,8 +111,6 @@ #define VSOMEIP_MINIMUM_CHECK_TTL_TIMEOUT 100 #define VSOMEIP_SETSOCKOPT_TIMEOUT_US 500000 // us -#define VSOMEIP_DEFAULT_EVENT_LOOP_PERIODICITY 0 // seconds - #define LOCAL_TCP_PORT_WAIT_TIME 100 #define LOCAL_TCP_PORT_MAX_WAIT_TIME 10000 diff --git a/implementation/configuration/src/configuration_impl.cpp b/implementation/configuration/src/configuration_impl.cpp index 0379c01ca..b318f1c37 100644 --- a/implementation/configuration/src/configuration_impl.cpp +++ b/implementation/configuration/src/configuration_impl.cpp @@ -894,7 +894,6 @@ void configuration_impl::load_application_data(const boost::property_tree::ptree std::size_t its_max_detached_thread_wait_time(VSOMEIP_MAX_WAIT_TIME_DETACHED_THREADS); std::size_t its_io_thread_count(VSOMEIP_DEFAULT_IO_THREAD_COUNT); std::size_t its_request_debounce_time(VSOMEIP_REQUEST_DEBOUNCE_TIME); - std::size_t its_event_loop_periodicity{VSOMEIP_DEFAULT_EVENT_LOOP_PERIODICITY}; std::map> plugins; int its_io_thread_nice_level(VSOMEIP_DEFAULT_IO_THREAD_NICE_LEVEL); debounce_configuration_t its_debounces; @@ -953,9 +952,6 @@ void configuration_impl::load_application_data(const boost::property_tree::ptree } } else if (its_key == "has_session_handling") { has_session_handling = (its_value != "false"); - } else if (its_key == "event_loop_periodicity") { - its_converter << std::dec << its_value; - its_converter >> its_event_loop_periodicity; } } if (its_name != "") { @@ -977,7 +973,6 @@ void configuration_impl::load_application_data(const boost::property_tree::ptree its_max_detached_thread_wait_time, its_io_thread_count, its_request_debounce_time, - its_event_loop_periodicity, plugins, its_io_thread_nice_level, its_debounces, @@ -3080,17 +3075,6 @@ bool configuration_impl::has_session_handling(const std::string& _name) const { return its_value; } -std::size_t configuration_impl::get_event_loop_periodicity(const std::string& _name) const { - std::size_t its_event_loop_periodicity = VSOMEIP_DEFAULT_EVENT_LOOP_PERIODICITY; - - auto found_application = applications_.find(_name); - if (found_application != applications_.end()) { - its_event_loop_periodicity = found_application->second.event_loop_periodicity_; - } - - return its_event_loop_periodicity; -} - std::set> configuration_impl::get_remote_services() const { std::lock_guard its_lock(services_mutex_); std::set> its_remote_services; diff --git a/implementation/endpoints/include/abstract_socket_factory.hpp b/implementation/endpoints/include/abstract_socket_factory.hpp index 2e2d024ca..4d7443e0f 100644 --- a/implementation/endpoints/include/abstract_socket_factory.hpp +++ b/implementation/endpoints/include/abstract_socket_factory.hpp @@ -6,15 +6,16 @@ #ifndef VSOMEIP_V3_ABSTRACT_SOCKET_FACTORY_HPP_ #define VSOMEIP_V3_ABSTRACT_SOCKET_FACTORY_HPP_ +#include "abstract_netlink_connector.hpp" +#include "abstract_timer.hpp" +#include "tcp_socket.hpp" + #include #if defined(__linux__) || defined(__QNX__) #include #endif #include -#include "abstract_netlink_connector.hpp" -#include "tcp_socket.hpp" - namespace vsomeip_v3 { class abstract_socket_factory { @@ -44,6 +45,8 @@ class abstract_socket_factory { return std::make_unique(_io); } #endif + + virtual std::unique_ptr create_timer(boost::asio::io_context& _io) = 0; }; // In order for this function to change the globally used abstract_socket_factory, diff --git a/implementation/endpoints/include/abstract_timer.hpp b/implementation/endpoints/include/abstract_timer.hpp new file mode 100644 index 000000000..49186a10e --- /dev/null +++ b/implementation/endpoints/include/abstract_timer.hpp @@ -0,0 +1,36 @@ +// Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef VSOMEIP_V3_ABSTRACT_TIMER_HPP_ +#define VSOMEIP_V3_ABSTRACT_TIMER_HPP_ + +#include + +#include +#include + +namespace vsomeip_v3 { + +/** + * abstraction for the boost::asio::steady_timer to allow for fake injections + **/ +class abstract_timer { +public: + using handler_t = std::function; + + virtual ~abstract_timer() = default; + + /// @see boost::asio::steady_timer::cancel() + virtual void cancel() = 0; + + /// @see boost::asio::steady_timer::expires_after() + virtual void expires_after(std::chrono::milliseconds _timeout) = 0; + + /// @see boost::asio::steady_timer::async_wait() + virtual void async_wait(handler_t _handler) = 0; +}; +} + +#endif diff --git a/implementation/endpoints/include/asio_socket_factory.hpp b/implementation/endpoints/include/asio_socket_factory.hpp index 12393e857..dba8307b2 100644 --- a/implementation/endpoints/include/asio_socket_factory.hpp +++ b/implementation/endpoints/include/asio_socket_factory.hpp @@ -23,6 +23,7 @@ class asio_socket_factory final : public abstract_socket_factory { std::unique_ptr create_tcp_socket(boost::asio::io_context& _io) override; std::unique_ptr create_tcp_acceptor(boost::asio::io_context& _io) override; + std::unique_ptr create_timer(boost::asio::io_context& _io) override; }; } diff --git a/implementation/endpoints/include/asio_tcp_socket.hpp b/implementation/endpoints/include/asio_tcp_socket.hpp index 0b577f95e..22ec4db1e 100644 --- a/implementation/endpoints/include/asio_tcp_socket.hpp +++ b/implementation/endpoints/include/asio_tcp_socket.hpp @@ -52,6 +52,10 @@ class asio_tcp_socket final : public tcp_socket { auto opt = static_cast(count); return setsockopt(socket_.native_handle(), IPPROTO_TCP, TCP_KEEPCNT, &opt, sizeof(opt)) != -1; } + [[nodiscard]] bool set_quick_ack() override { + int flag = 1; + return setsockopt(socket_.native_handle(), IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(flag)) != -1; + } #endif #if defined(__linux__) || defined(__QNX__) [[nodiscard]] bool bind_to_device(std::string const& _device) override { diff --git a/implementation/endpoints/include/asio_timer.hpp b/implementation/endpoints/include/asio_timer.hpp new file mode 100644 index 000000000..3f0267725 --- /dev/null +++ b/implementation/endpoints/include/asio_timer.hpp @@ -0,0 +1,30 @@ +// Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef VSOMEIP_V3_ASIO_TIMER_HPP_ +#define VSOMEIP_V3_ASIO_TIMER_HPP_ + +#include "abstract_timer.hpp" +#include + +namespace vsomeip_v3 { + +class asio_timer : public abstract_timer { +public: + asio_timer(boost::asio::io_context& _io) : timer_(_io) { } + virtual ~asio_timer() override = default; + +private: + virtual void cancel() override { timer_.cancel(); } + + virtual void expires_after(std::chrono::milliseconds _timeout) override { timer_.expires_after(_timeout); } + + virtual void async_wait(handler_t _handler) override { timer_.async_wait(_handler); } + + boost::asio::steady_timer timer_; +}; +} + +#endif diff --git a/implementation/endpoints/include/client_endpoint_impl.hpp b/implementation/endpoints/include/client_endpoint_impl.hpp index 2bd684222..cd4215a23 100644 --- a/implementation/endpoints/include/client_endpoint_impl.hpp +++ b/implementation/endpoints/include/client_endpoint_impl.hpp @@ -55,7 +55,7 @@ class client_endpoint_impl : public endpoint_impl, bool flush(); void prepare_stop(const endpoint::prepare_stop_handler_t& _handler, service_t _service); - virtual void stop(); + virtual void stop(bool _due_to_error); virtual void restart(bool _force = false) = 0; bool is_client() const; @@ -95,7 +95,7 @@ class client_endpoint_impl : public endpoint_impl, virtual void send_queued(std::pair& _entry) = 0; virtual void get_configured_times_from_endpoint(service_t _service, method_t _method, std::chrono::nanoseconds* _debouncing, std::chrono::nanoseconds* _maximum_retention) const = 0; - void shutdown_and_close_socket(bool _recreate_socket, bool _is_error = false); + void shutdown_and_close_socket(bool _recreate_socket, bool _due_to_error); void shutdown_and_close_socket_unlocked(bool _recreate_socket); void start_connect_timer(); void start_connecting_timer(); diff --git a/implementation/endpoints/include/endpoint.hpp b/implementation/endpoints/include/endpoint.hpp index 09cfcf548..7a3215d09 100644 --- a/implementation/endpoints/include/endpoint.hpp +++ b/implementation/endpoints/include/endpoint.hpp @@ -26,7 +26,12 @@ class endpoint { virtual void start() = 0; virtual void restart(bool _force = false) = 0; - virtual void stop() = 0; + /** + * @brief Stop endpoint + * + * @param _due_to_error if true, we are stopping due to an error - do not bother with graceful closure/ensuring message delivery/etc + */ + virtual void stop(bool _due_to_error) = 0; virtual void prepare_stop(const prepare_stop_handler_t& _handler, service_t _service = ANY_SERVICE) = 0; @@ -36,7 +41,6 @@ class endpoint { virtual bool send(const byte_t* _data, uint32_t _size) = 0; virtual bool send_to(const std::shared_ptr _target, const byte_t* _data, uint32_t _size) = 0; virtual bool send_error(const std::shared_ptr _target, const byte_t* _data, uint32_t _size) = 0; - virtual void enable_magic_cookies() = 0; virtual void receive() = 0; virtual bool wait_connecting_timer() { return true; }; diff --git a/implementation/endpoints/include/endpoint_impl.hpp b/implementation/endpoints/include/endpoint_impl.hpp index 53f0f7094..71e021f6a 100644 --- a/implementation/endpoints/include/endpoint_impl.hpp +++ b/implementation/endpoints/include/endpoint_impl.hpp @@ -33,8 +33,6 @@ class endpoint_impl : public virtual endpoint { endpoint_impl(endpoint_impl const&&) = delete; virtual ~endpoint_impl() = default; - void enable_magic_cookies(); - void add_default_target(service_t, const std::string&, uint16_t); void remove_default_target(service_t); void remove_stop_handler(service_t); @@ -67,19 +65,10 @@ class endpoint_impl : public virtual endpoint { std::weak_ptr endpoint_host_; std::weak_ptr routing_host_; - bool is_supporting_magic_cookies_; - std::atomic has_enabled_magic_cookies_; - - // Filter configuration - std::map opened_; - std::uint32_t max_message_size_; - std::atomic use_count_; - std::atomic sending_blocked_; - std::mutex local_mutex_; endpoint_type local_; error_handler_t error_handler_; diff --git a/implementation/endpoints/include/endpoint_manager_base.hpp b/implementation/endpoints/include/endpoint_manager_base.hpp index 8f4e2c6fc..3b160be37 100644 --- a/implementation/endpoints/include/endpoint_manager_base.hpp +++ b/implementation/endpoints/include/endpoint_manager_base.hpp @@ -32,7 +32,7 @@ class endpoint_manager_base : public std::enable_shared_from_this create_local(client_t _client); - void remove_local(client_t _client); + void remove_local(client_t _client, bool _remove_due_to_error); std::shared_ptr find_or_create_local(client_t _client); std::shared_ptr find_local(client_t _client); diff --git a/implementation/endpoints/include/local_tcp_client_endpoint_impl.hpp b/implementation/endpoints/include/local_tcp_client_endpoint_impl.hpp index 7f15ce375..382ace37d 100644 --- a/implementation/endpoints/include/local_tcp_client_endpoint_impl.hpp +++ b/implementation/endpoints/include/local_tcp_client_endpoint_impl.hpp @@ -24,7 +24,7 @@ class local_tcp_client_endpoint_impl : public local_tcp_client_endpoint_base_imp virtual ~local_tcp_client_endpoint_impl(); void start(); - void stop(); + void stop(bool _due_to_error); bool is_local() const; @@ -46,8 +46,6 @@ class local_tcp_client_endpoint_impl : public local_tcp_client_endpoint_base_imp private: void send_queued(std::pair& _entry); - void send_magic_cookie(); - void connect(); void receive(); void receive_cbk(boost::system::error_code const& _error, std::size_t _bytes); diff --git a/implementation/endpoints/include/local_tcp_server_endpoint_impl.hpp b/implementation/endpoints/include/local_tcp_server_endpoint_impl.hpp index 94f046f74..dcca44b7f 100644 --- a/implementation/endpoints/include/local_tcp_server_endpoint_impl.hpp +++ b/implementation/endpoints/include/local_tcp_server_endpoint_impl.hpp @@ -37,7 +37,7 @@ class local_tcp_server_endpoint_impl : public local_tcp_server_endpoint_base_imp void deinit(); void start(); - void stop(); + void stop(bool _due_to_error); void receive(); @@ -103,7 +103,7 @@ class local_tcp_server_endpoint_impl : public local_tcp_server_endpoint_base_imp std::string get_path_local() const; std::string get_path_remote() const; void handle_recv_buffer_exception(const std::exception& _e); - void shutdown_and_close(bool _is_error = false); + void shutdown_and_close(bool _due_to_error); void shutdown_and_close_unlocked(); std::mutex socket_mutex_; @@ -142,7 +142,7 @@ class local_tcp_server_endpoint_impl : public local_tcp_server_endpoint_base_imp private: void init_unlocked(const endpoint_type& _local, boost::system::error_code& _error); void add_connection(const client_t& _client, const std::shared_ptr& _connection); - void remove_connection(const client_t& _client); + void remove_connection(const client_t& _client, connection* _connection); void accept_cbk(connection::ptr _connection, boost::system::error_code const& _error); std::string get_remote_information(const target_data_iterator_type _queue_iterator) const; std::string get_remote_information(const endpoint_type& _remote) const; diff --git a/implementation/endpoints/include/local_uds_client_endpoint_impl.hpp b/implementation/endpoints/include/local_uds_client_endpoint_impl.hpp index e54d4f1ea..6129d54b9 100644 --- a/implementation/endpoints/include/local_uds_client_endpoint_impl.hpp +++ b/implementation/endpoints/include/local_uds_client_endpoint_impl.hpp @@ -24,7 +24,7 @@ class local_uds_client_endpoint_impl : public local_uds_client_endpoint_base_imp virtual ~local_uds_client_endpoint_impl() = default; void start(); - void stop(); + void stop(bool _due_to_error); bool is_local() const; @@ -45,8 +45,6 @@ class local_uds_client_endpoint_impl : public local_uds_client_endpoint_base_imp private: void send_queued(std::pair& _entry); - void send_magic_cookie(); - void connect(); void receive(); void receive_cbk(boost::system::error_code const& _error, std::size_t _bytes); diff --git a/implementation/endpoints/include/local_uds_server_endpoint_impl.hpp b/implementation/endpoints/include/local_uds_server_endpoint_impl.hpp index 149c53f4a..6fc4fd12e 100644 --- a/implementation/endpoints/include/local_uds_server_endpoint_impl.hpp +++ b/implementation/endpoints/include/local_uds_server_endpoint_impl.hpp @@ -34,7 +34,7 @@ class local_uds_server_endpoint_impl : public local_uds_server_endpoint_base_imp void deinit(); void start(); - void stop(); + void stop(bool _due_to_error); void receive(); diff --git a/implementation/endpoints/include/server_endpoint_impl.hpp b/implementation/endpoints/include/server_endpoint_impl.hpp index a3e9e26ec..e745ebcea 100644 --- a/implementation/endpoints/include/server_endpoint_impl.hpp +++ b/implementation/endpoints/include/server_endpoint_impl.hpp @@ -67,7 +67,7 @@ class server_endpoint_impl : public server_endpoint, virtual ~server_endpoint_impl() = default; virtual void init(const endpoint_type& _local, boost::system::error_code& _error) = 0; - virtual void stop(); + virtual void stop(bool _due_to_error); bool is_client() const; void restart(bool _force); diff --git a/implementation/endpoints/include/tcp_client_endpoint_impl.hpp b/implementation/endpoints/include/tcp_client_endpoint_impl.hpp index b6b9e51f4..cdcb99b2d 100644 --- a/implementation/endpoints/include/tcp_client_endpoint_impl.hpp +++ b/implementation/endpoints/include/tcp_client_endpoint_impl.hpp @@ -25,7 +25,7 @@ class tcp_client_endpoint_impl : public tcp_client_endpoint_base_impl { public: tcp_client_endpoint_impl(const std::shared_ptr& _endpoint_host, const std::shared_ptr& _routing_host, const endpoint_type& _local, const endpoint_type& _remote, boost::asio::io_context& _io, - const std::shared_ptr& _configuration); + const std::shared_ptr& _configuration, bool _use_magic_cookies); virtual ~tcp_client_endpoint_impl(); void init(); @@ -69,6 +69,10 @@ class tcp_client_endpoint_impl : public tcp_client_endpoint_base_impl { void wait_until_sent(const boost::system::error_code& _error); + // magic cookie state; both are modified under `socket_mutex_` + bool use_magic_cookies_ = false; + std::chrono::steady_clock::time_point last_cookie_sent_; + const std::uint32_t recv_buffer_size_initial_; message_buffer_ptr_t recv_buffer_; std::uint32_t shrink_count_; @@ -76,7 +80,6 @@ class tcp_client_endpoint_impl : public tcp_client_endpoint_base_impl { const boost::asio::ip::address remote_address_; const std::uint16_t remote_port_; - std::chrono::steady_clock::time_point last_cookie_sent_; const std::chrono::milliseconds send_timeout_; const std::chrono::milliseconds send_timeout_warning_; diff --git a/implementation/endpoints/include/tcp_server_endpoint_impl.hpp b/implementation/endpoints/include/tcp_server_endpoint_impl.hpp index 02f47eca9..866b7f728 100644 --- a/implementation/endpoints/include/tcp_server_endpoint_impl.hpp +++ b/implementation/endpoints/include/tcp_server_endpoint_impl.hpp @@ -29,12 +29,12 @@ class tcp_server_endpoint_impl : public tcp_server_endpoint_base_impl { public: tcp_server_endpoint_impl(const std::shared_ptr& _endpoint_host, const std::shared_ptr& _routing_host, - boost::asio::io_context& _io, const std::shared_ptr& _configuration); + boost::asio::io_context& _io, const std::shared_ptr& _configuration, bool _use_magic_cookies); virtual ~tcp_server_endpoint_impl(); void init(const endpoint_type& _local, boost::system::error_code& _error); void start(); - void stop(); + void stop(bool _due_to_error); bool send_to(const std::shared_ptr _target, const byte_t* _data, uint32_t _size); bool send_error(const std::shared_ptr _target, const byte_t* _data, uint32_t _size); @@ -67,7 +67,7 @@ class tcp_server_endpoint_impl : public tcp_server_endpoint_base_impl { typedef std::shared_ptr ptr; static ptr create(const std::weak_ptr& _server, std::uint32_t _max_message_size, - std::uint32_t _buffer_shrink_threshold, bool _magic_cookies_enabled, boost::asio::io_context& _io, + std::uint32_t _buffer_shrink_threshold, bool _use_magic_cookies, boost::asio::io_context& _io, std::chrono::milliseconds _send_timeout); ~connection(); @@ -87,7 +87,7 @@ class tcp_server_endpoint_impl : public tcp_server_endpoint_base_impl { private: connection(const std::weak_ptr& _server, std::uint32_t _max_message_size, - std::uint32_t _recv_buffer_size_initial, std::uint32_t _buffer_shrink_threshold, bool _magic_cookies_enabled, + std::uint32_t _recv_buffer_size_initial, std::uint32_t _buffer_shrink_threshold, bool _use_magic_cookies, boost::asio::io_context& _io, std::chrono::milliseconds _send_timeout); bool send_magic_cookie(message_buffer_ptr_t& _buffer); bool is_magic_cookie(size_t _offset) const; @@ -117,14 +117,18 @@ class tcp_server_endpoint_impl : public tcp_server_endpoint_base_impl { endpoint_type remote_; boost::asio::ip::address remote_address_; std::uint16_t remote_port_; - std::atomic magic_cookies_enabled_; + + // magic cookie state; both are modified under `socket_mutex_` + bool use_magic_cookies_ = false; std::chrono::steady_clock::time_point last_cookie_sent_; + const std::chrono::milliseconds send_timeout_; const std::chrono::milliseconds send_timeout_warning_; std::string instance_name_; }; + const bool use_magic_cookies_ = false; std::mutex acceptor_mutex_; boost::asio::ip::tcp::acceptor acceptor_; std::mutex connections_mutex_; @@ -136,7 +140,7 @@ class tcp_server_endpoint_impl : public tcp_server_endpoint_base_impl { std::string instance_name_; private: - void remove_connection(connection* _connection); + void remove_connection(endpoint_type _endpoint, connection* _connection); void accept_cbk(connection::ptr _connection, boost::system::error_code const& _error); std::string get_remote_information(const target_data_iterator_type _it) const; std::string get_remote_information(const endpoint_type& _remote) const; diff --git a/implementation/endpoints/include/tcp_socket.hpp b/implementation/endpoints/include/tcp_socket.hpp index 6d681c46f..f2059b78e 100644 --- a/implementation/endpoints/include/tcp_socket.hpp +++ b/implementation/endpoints/include/tcp_socket.hpp @@ -92,6 +92,12 @@ class tcp_socket : public tcp_base_socket { * On error errno will be set. **/ [[nodiscard]] virtual bool set_keepcnt(uint32_t) = 0; + /** + * abstraction for setting the linux specific tcp option + * TCP_QUICKACK. + * On error errno will be set. + **/ + [[nodiscard]] virtual bool set_quick_ack() = 0; #endif #if defined(__linux__) || defined(__QNX__) /** diff --git a/implementation/endpoints/include/timer.hpp b/implementation/endpoints/include/timer.hpp new file mode 100644 index 000000000..889f1f49c --- /dev/null +++ b/implementation/endpoints/include/timer.hpp @@ -0,0 +1,115 @@ +// Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef VSOMEIP_V3_TIMER_HPP_ +#define VSOMEIP_V3_TIMER_HPP_ + +#include "abstract_timer.hpp" + +#include +#include +#include +#include +#include + +namespace vsomeip_v3 { + +/** + * Convenience class to schedule a task based on the abstract_timer. + **/ +class timer : public std::enable_shared_from_this { +private: + struct hidden { }; + +public: + using task_t = std::function; + + /** + * Use timer::create instead + **/ + explicit timer(hidden _h, std::unique_ptr _timer, std::chrono::milliseconds _interval, task_t _task); + + ~timer(); + + /** + * Creates a timer that is ready to be started. + * + * @param _interval, time to wait before executing the passed in _task. + * @param _task, if task returns true, the timer will restart itself. + * + * Note: + * When the timer managed by the shared_ptr is deleted the timer is stopped. + * But the timer is guaranteed to outlive the execution of any running task + * (a task is not considered running, when the timer is currently awaiting + * the interval to expire). + * To avoid circular shared_ptr ownership (requiring to manually clear timer handles), + * it is advised to capture weak references to the used objects within the task. + * Warning: + * The timer will not check the validity of the handed in task + **/ + static std::shared_ptr create(boost::asio::io_context& _io, std::chrono::milliseconds _interval, task_t _task); + + /** + * Starts the timer. If no further action is taken the following sequence can be expected: + * 1. awaiting the timeout + * 2. copy the shared_ptr of the timer + * 3. execute the task + * 4. if the task return true -> restart the timer, else stop + * 5. delete copy of the shared_ptr + * + * If the timer had been started before then: + * 1. If the timer is still waiting, the timer is restarted. + * 2. If the timer is currently waiting for the execution of the task to finish, then the timer will be + * restarted afterwards irrespective of the return of the task itself. + **/ + void start(); + + /** + * Tries to stop the running timer. + * If the timer is currently waiting to expire: The task will not be scheduled. + * If the timer is currently executing the task: After the task is done the timer will not be + * restarted, irrespective of the return of the task itself. + **/ + void stop(); + + /** + * Whether the timer was started and did not finish the execution of the task yet. + * Note: In case the timer should be stopped, calling stop is sufficient + **/ + [[nodiscard]] bool is_running() const; + + /** + * Adjusts the interval of the timer. + * Only allowed if the timer is currently stopped. + * + * @return false, if the timer is currently trying to execute the task, or started + **/ + [[nodiscard]] bool set_interval(std::chrono::milliseconds _interval); + + /** + * Adjusts the task the timer stores. + * Beware about the notes in to timer::create about object lifetime. + * + * @return false, if the timer is currently trying to execute the task, or started + **/ + [[nodiscard]] bool set_task(task_t _task); + +private: + enum class state_e { STOPPED, STARTED, IN_TASK, IN_TASK_STARTED, IN_TASK_STOPPED }; + + void stop_unlocked(); + void start_unlocked(); + + void handle(boost::system::error_code const& _ec); + + state_e state_{state_e::STOPPED}; + std::unique_ptr timer_; + std::chrono::milliseconds interval_; + task_t task_; + mutable std::mutex mtx_; +}; +} + +#endif diff --git a/implementation/endpoints/include/udp_server_endpoint_impl.hpp b/implementation/endpoints/include/udp_server_endpoint_impl.hpp index 3b1ec8ff7..9670a7afd 100644 --- a/implementation/endpoints/include/udp_server_endpoint_impl.hpp +++ b/implementation/endpoints/include/udp_server_endpoint_impl.hpp @@ -35,7 +35,7 @@ class udp_server_endpoint_impl : public udp_server_endpoint_base_impl { void init(const endpoint_type& _local, boost::system::error_code& _error) override; void start() override; - void stop() override; + void stop(bool _due_to_error) override; void restart(bool _force) override; void receive() override; diff --git a/implementation/endpoints/include/virtual_server_endpoint_impl.hpp b/implementation/endpoints/include/virtual_server_endpoint_impl.hpp index ae0d24bb0..62707ffaa 100644 --- a/implementation/endpoints/include/virtual_server_endpoint_impl.hpp +++ b/implementation/endpoints/include/virtual_server_endpoint_impl.hpp @@ -20,7 +20,7 @@ class virtual_server_endpoint_impl : public endpoint, public std::enable_shared_ void start(); void prepare_stop(const endpoint::prepare_stop_handler_t& _handler, service_t _service); - void stop(); + void stop(bool _due_to_error); bool is_established() const; bool is_established_or_connected() const; @@ -30,7 +30,6 @@ class virtual_server_endpoint_impl : public endpoint, public std::enable_shared_ bool send(const byte_t* _data, uint32_t _size); bool send_to(const std::shared_ptr _target, const byte_t* _data, uint32_t _size); bool send_error(const std::shared_ptr _target, const byte_t* _data, uint32_t _size); - void enable_magic_cookies(); void receive(); void add_default_target(service_t _service, const std::string& _address, uint16_t _port); diff --git a/implementation/endpoints/src/asio_socket_factory.cpp b/implementation/endpoints/src/asio_socket_factory.cpp index 8dd7197e5..716249671 100644 --- a/implementation/endpoints/src/asio_socket_factory.cpp +++ b/implementation/endpoints/src/asio_socket_factory.cpp @@ -5,6 +5,7 @@ #include "../include/asio_socket_factory.hpp" #include "../include/asio_tcp_socket.hpp" +#include "../include/asio_timer.hpp" #include "../include/netlink_connector.hpp" namespace vsomeip_v3 { @@ -25,4 +26,7 @@ std::unique_ptr asio_socket_factory::create_tcp_acceptor(boost::as return std::make_unique(_io); } +std::unique_ptr asio_socket_factory::create_timer(boost::asio::io_context& _io) { + return std::make_unique(_io); +} } diff --git a/implementation/endpoints/src/client_endpoint_impl.cpp b/implementation/endpoints/src/client_endpoint_impl.cpp index 6154fe606..5d4736040 100644 --- a/implementation/endpoints/src/client_endpoint_impl.cpp +++ b/implementation/endpoints/src/client_endpoint_impl.cpp @@ -117,7 +117,7 @@ void client_endpoint_impl::prepare_stop(const endpoint::prepare_stop_h } template -void client_endpoint_impl::stop() { +void client_endpoint_impl::stop(bool _due_to_error) { { std::lock_guard its_lock(mutex_); endpoint_impl::sending_blocked_ = true; @@ -132,7 +132,8 @@ void client_endpoint_impl::stop() { connect_timeout_ = VSOMEIP_DEFAULT_CONNECT_TIMEOUT; // bind to strand as stop() might be called from different thread - boost::asio::dispatch(strand_, std::bind(&client_endpoint_impl::shutdown_and_close_socket, this->shared_from_this(), false, true)); + boost::asio::dispatch(strand_, + std::bind(&client_endpoint_impl::shutdown_and_close_socket, this->shared_from_this(), false, _due_to_error)); } template @@ -391,7 +392,7 @@ void client_endpoint_impl::connect_cbk(boost::system::error_code const if (_error == boost::asio::error::operation_aborted || endpoint_impl::sending_blocked_) { VSOMEIP_WARNING << "cei::" << __func__ << ": endpoint stopped, remote: " << get_remote_information() << ", endpoint > " << this << " socket state > " << to_string(state_.load()); - shutdown_and_close_socket(false); + shutdown_and_close_socket(false, false); return; } std::shared_ptr its_host = this->endpoint_host_.lock(); @@ -606,7 +607,7 @@ void client_endpoint_impl::send_cbk(boost::system::error_code const& _ } // endpoint was stopped endpoint_impl::sending_blocked_ = true; - shutdown_and_close_socket(false); + shutdown_and_close_socket(false, false); } else { service_t its_service(0); method_t its_method(0); @@ -640,7 +641,7 @@ void client_endpoint_impl::flush_cbk(boost::system::error_code const& } template -void client_endpoint_impl::shutdown_and_close_socket(bool _recreate_socket, bool _is_error) { +void client_endpoint_impl::shutdown_and_close_socket(bool _recreate_socket, bool _due_to_error) { #if defined(__linux__) || defined(__QNX__) boost::system::error_code its_error; @@ -666,7 +667,7 @@ void client_endpoint_impl::shutdown_and_close_socket(bool _recreate_so const auto send_buffer_size = send_buffer_size_cmd.get(); if (send_buffer_size > 0) { // shutdown_and_close_socket was called on error, do not wait to send remaining data - if (_is_error) { + if (_due_to_error) { VSOMEIP_WARNING << "cei::" << __func__ << ": error on socket, not waiting to send remaining data, remote: " << get_remote_information() << ", endpoint > " << this << " socket state > " << to_string(state_.load()); diff --git a/implementation/endpoints/src/endpoint_impl.cpp b/implementation/endpoints/src/endpoint_impl.cpp index 0704aa437..0087d4672 100644 --- a/implementation/endpoints/src/endpoint_impl.cpp +++ b/implementation/endpoints/src/endpoint_impl.cpp @@ -21,15 +21,9 @@ template endpoint_impl::endpoint_impl(const std::shared_ptr& _endpoint_host, const std::shared_ptr& _routing_host, boost::asio::io_context& _io, const std::shared_ptr& _configuration) : - io_(_io), endpoint_host_(_endpoint_host), routing_host_(_routing_host), is_supporting_magic_cookies_(false), - has_enabled_magic_cookies_(false), use_count_(0), sending_blocked_(false), configuration_(_configuration), + io_(_io), endpoint_host_(_endpoint_host), routing_host_(_routing_host), sending_blocked_(false), configuration_(_configuration), is_supporting_someip_tp_(false) { } -template -void endpoint_impl::enable_magic_cookies() { - has_enabled_magic_cookies_ = is_supporting_magic_cookies_; -} - template uint32_t endpoint_impl::find_magic_cookie(byte_t* _buffer, size_t _size) { bool is_found(false); diff --git a/implementation/endpoints/src/endpoint_manager_base.cpp b/implementation/endpoints/src/endpoint_manager_base.cpp index 6a2e39d08..90a2ece77 100644 --- a/implementation/endpoints/src/endpoint_manager_base.cpp +++ b/implementation/endpoints/src/endpoint_manager_base.cpp @@ -34,13 +34,13 @@ std::shared_ptr endpoint_manager_base::create_local(client_t _client) return create_local_unlocked(_client); } -void endpoint_manager_base::remove_local(const client_t _client) { +void endpoint_manager_base::remove_local(const client_t _client, bool _remove_due_to_error) { VSOMEIP_INFO << "emb::" << __func__ << ": self " << std::hex << std::setfill('0') << std::setw(4) << rm_->get_client() << ", client " - << _client; + << _client << ", error " << _remove_due_to_error; std::shared_ptr its_endpoint{find_local(_client)}; if (its_endpoint) { its_endpoint->register_error_handler(nullptr); - its_endpoint->stop(); + its_endpoint->stop(_remove_due_to_error); VSOMEIP_INFO << "Client [" << std::hex << rm_->get_client() << "] is closing connection to [" << std::hex << _client << "]" << " endpoint > " << its_endpoint; std::scoped_lock its_lock(local_endpoint_mutex_); diff --git a/implementation/endpoints/src/endpoint_manager_impl.cpp b/implementation/endpoints/src/endpoint_manager_impl.cpp index 2b6588098..13fcb49a6 100644 --- a/implementation/endpoints/src/endpoint_manager_impl.cpp +++ b/implementation/endpoints/src/endpoint_manager_impl.cpp @@ -185,7 +185,7 @@ void endpoint_manager_impl::add_remote_service_info(service_t _service, instance if (must_report) static_cast(rm_)->service_endpoint_connected(_service, _instance, its_info->get_major(), - its_info->get_minor(), its_endpoint, false); + its_info->get_minor(), its_endpoint); } void endpoint_manager_impl::add_remote_service_info(service_t _service, instance_t _instance, @@ -212,9 +212,9 @@ void endpoint_manager_impl::add_remote_service_info(service_t _service, instance if (must_report) { static_cast(rm_)->service_endpoint_connected(_service, _instance, its_info->get_major(), - its_info->get_minor(), its_unreliable, false); + its_info->get_minor(), its_unreliable); static_cast(rm_)->service_endpoint_connected(_service, _instance, its_info->get_major(), - its_info->get_minor(), its_reliable, false); + its_info->get_minor(), its_reliable); } } @@ -252,15 +252,14 @@ std::shared_ptr endpoint_manager_impl::create_server_endpoint(uint16_t std::lock_guard its_lock(endpoint_mutex_); if (_start) { if (_reliable) { - auto its_tmp{std::make_shared(shared_from_this(), rm_->shared_from_this(), io_, configuration_)}; + bool its_magic_cookies_enabled = configuration_->has_enabled_magic_cookies(its_unicast_str, _port) + || configuration_->has_enabled_magic_cookies("local", _port); + auto its_tmp{std::make_shared(shared_from_this(), rm_->shared_from_this(), io_, configuration_, + its_magic_cookies_enabled)}; if (its_tmp) { boost::asio::ip::tcp::endpoint its_reliable(its_unicast, _port); its_tmp->init(its_reliable, its_error); if (!its_error) { - if (configuration_->has_enabled_magic_cookies(its_unicast_str, _port) - || configuration_->has_enabled_magic_cookies("local", _port)) { - its_tmp->enable_magic_cookies(); - } its_server_endpoint = its_tmp; } } @@ -454,7 +453,7 @@ void endpoint_manager_impl::clear_client_endpoints(service_t _service, instance_ if (!other_services_reachable_through_endpoint && its_endpoint) { release_used_client_port(its_remote_address, its_remote_port, _reliable, its_local_port); - its_endpoint->stop(); + its_endpoint->stop(false); } } @@ -540,7 +539,7 @@ void endpoint_manager_impl::clear_multicast_endpoints(service_t _service, instan its_udp_server_endpoint->leave(its_address); if (!is_used_endpoint(its_multicast_endpoint.get())) - its_multicast_endpoint->stop(); + its_multicast_endpoint->stop(false); } } @@ -824,7 +823,6 @@ void endpoint_manager_impl::on_connect(std::shared_ptr _endpoint) { major_version_t major_; minor_version_t minor_; std::shared_ptr endpoint_; - bool service_is_unreliable_only_; }; // Set to state CONNECTED as connection is not yet fully established in remote side POV @@ -852,9 +850,8 @@ void endpoint_manager_impl::on_connect(std::shared_ptr _endpoint) { const auto its_other_endpoint = its_info->get_endpoint(!endpoint_is_reliable); if (!its_other_endpoint || (its_other_endpoint && its_other_endpoint->is_established_or_connected())) { - services_to_report_.push_front({its_service.first, its_instance.first, its_info->get_major(), - its_info->get_minor(), _endpoint, - (!endpoint_is_reliable && !its_other_endpoint)}); + services_to_report_.push_front( + {its_service.first, its_instance.first, its_info->get_major(), its_info->get_minor(), _endpoint}); } } } @@ -862,8 +859,7 @@ void endpoint_manager_impl::on_connect(std::shared_ptr _endpoint) { } } for (const auto& s : services_to_report_) { - static_cast(rm_)->service_endpoint_connected(s.service_id_, s.instance_id_, s.major_, s.minor_, s.endpoint_, - s.service_is_unreliable_only_); + static_cast(rm_)->service_endpoint_connected(s.service_id_, s.instance_id_, s.major_, s.minor_, s.endpoint_); } if (services_to_report_.empty()) { _endpoint->set_established(true); @@ -1094,13 +1090,10 @@ std::shared_ptr endpoint_manager_impl::create_client_endpoint(const bo try { if (_reliable) { + bool its_use_magic_cookies = configuration_->has_enabled_magic_cookies(_address.to_string(), _remote_port); its_endpoint = std::make_shared( shared_from_this(), rm_->shared_from_this(), boost::asio::ip::tcp::endpoint(its_unicast, _local_port), - boost::asio::ip::tcp::endpoint(_address, _remote_port), io_, configuration_); - - if (configuration_->has_enabled_magic_cookies(_address.to_string(), _remote_port)) { - its_endpoint->enable_magic_cookies(); - } + boost::asio::ip::tcp::endpoint(_address, _remote_port), io_, configuration_, its_use_magic_cookies); } else { its_endpoint = std::make_shared( shared_from_this(), rm_->shared_from_this(), boost::asio::ip::udp::endpoint(its_unicast, _local_port), @@ -1279,7 +1272,7 @@ void endpoint_manager_impl::suspend() { for (const auto& its_weak : weak_endpoints) { if (auto its_endpoint = its_weak.lock()) { - its_endpoint->stop(); + its_endpoint->stop(false); } } } diff --git a/implementation/endpoints/src/local_client_endpoint_impl.cpp b/implementation/endpoints/src/local_client_endpoint_impl.cpp index 80a960ea0..6368e4cb9 100644 --- a/implementation/endpoints/src/local_client_endpoint_impl.cpp +++ b/implementation/endpoints/src/local_client_endpoint_impl.cpp @@ -62,7 +62,7 @@ void local_client_endpoint_impl::error_handler() { if (client_endpoint_impl::is_established_or_connected()) { std::lock_guard its_lock(endpoint_impl::error_handler_mutex_); handler = endpoint_impl::error_handler_; - client_endpoint_impl::shutdown_and_close_socket(false); + client_endpoint_impl::shutdown_and_close_socket(false, true); } else { VSOMEIP_INFO << "lcei::" << __func__ << " connection no longer established/connected " << this; } diff --git a/implementation/endpoints/src/local_tcp_client_endpoint_impl.cpp b/implementation/endpoints/src/local_tcp_client_endpoint_impl.cpp index 9b6f1f2fb..08e9263ef 100644 --- a/implementation/endpoints/src/local_tcp_client_endpoint_impl.cpp +++ b/implementation/endpoints/src/local_tcp_client_endpoint_impl.cpp @@ -35,8 +35,6 @@ local_tcp_client_endpoint_impl::local_tcp_client_endpoint_impl(const std::shared local_tcp_client_endpoint_base_impl(_endpoint_host, _routing_host, _local, _remote, _io, _configuration), recv_buffer_(VSOMEIP_LOCAL_CLIENT_ENDPOINT_RECV_BUFFER_SIZE, 0) { - is_supporting_magic_cookies_ = false; - this->max_message_size_ = _configuration->get_max_message_size_local(); this->queue_limit_ = _configuration->get_endpoint_queue_limit_local(); } @@ -45,7 +43,7 @@ local_tcp_client_endpoint_impl::~local_tcp_client_endpoint_impl() { // ensure socket close() before boost destructor // otherwise boost asio removes linger, which may leave connection in TIME_WAIT VSOMEIP_INFO << "ltcei::~ltcei: endpoint > " << this << ", state_ > " << to_string(state_.load()); - shutdown_and_close_socket(false); + shutdown_and_close_socket(false, true); } bool local_tcp_client_endpoint_impl::is_local() const { @@ -88,7 +86,7 @@ void local_tcp_client_endpoint_impl::start() { } } -void local_tcp_client_endpoint_impl::stop() { +void local_tcp_client_endpoint_impl::stop(bool _due_to_error) { { std::lock_guard its_lock(mutex_); sending_blocked_ = true; @@ -112,7 +110,7 @@ void local_tcp_client_endpoint_impl::stop() { while (times_slept <= LOCAL_TCP_WAIT_SEND_QUEUE_ON_STOP) { std::unique_lock its_lock(mutex_); queue_size = queue_.size(); - if (queue_size == 0) { + if (queue_size == 0 || _due_to_error) { break; } else { queue_cv_.wait_for(its_lock, std::chrono::milliseconds(10), [this] { return queue_.size() == 0; }); @@ -121,10 +119,14 @@ void local_tcp_client_endpoint_impl::stop() { } if (queue_size != 0) { - VSOMEIP_ERROR << "ltcei::" << __func__ << ": stopping with " << queue_size << " bytes in queue, endpoint > " << this; + if (_due_to_error) { + VSOMEIP_WARNING << "ltcei::" << __func__ << ": stopping with " << queue_size << " bytes in queue, endpoint > " << this; + } else { + VSOMEIP_ERROR << "ltcei::" << __func__ << ": stopping with " << queue_size << " bytes in queue, endpoint > " << this; + } } } - shutdown_and_close_socket(false); + shutdown_and_close_socket(false, _due_to_error); } void local_tcp_client_endpoint_impl::connect() { @@ -302,8 +304,6 @@ void local_tcp_client_endpoint_impl::get_configured_times_from_endpoint(service_ VSOMEIP_ERROR << "ltcei::get_configured_times_from_endpoint called." << " endpoint > " << this; } -void local_tcp_client_endpoint_impl::send_magic_cookie() { } - void local_tcp_client_endpoint_impl::receive_cbk(boost::system::error_code const& _error, std::size_t _bytes) { if (_error) { diff --git a/implementation/endpoints/src/local_tcp_server_endpoint_impl.cpp b/implementation/endpoints/src/local_tcp_server_endpoint_impl.cpp index 72aef9af6..9bb4c2d7c 100644 --- a/implementation/endpoints/src/local_tcp_server_endpoint_impl.cpp +++ b/implementation/endpoints/src/local_tcp_server_endpoint_impl.cpp @@ -44,7 +44,6 @@ local_tcp_server_endpoint_impl::local_tcp_server_endpoint_impl(const std::shared local_tcp_server_endpoint_base_impl(_endpoint_host, _routing_host, _io, _configuration), acceptor_(abstract_socket_factory::get()->create_tcp_acceptor(_io)), buffer_shrink_threshold_(_configuration->get_buffer_shrink_threshold()), is_routing_endpoint_(_is_routing_endpoint) { - is_supporting_magic_cookies_ = false; this->max_message_size_ = _configuration->get_max_message_size_local(); this->queue_limit_ = _configuration->get_endpoint_queue_limit_local(); @@ -112,9 +111,8 @@ void local_tcp_server_endpoint_impl::start() { } } -void local_tcp_server_endpoint_impl::stop() { +void local_tcp_server_endpoint_impl::stop(bool /*_due_to_error*/) { - server_endpoint_impl::stop(); { std::scoped_lock its_lock{acceptor_mutex_}; if (acceptor_->is_open()) { @@ -199,22 +197,49 @@ bool local_tcp_server_endpoint_impl::get_default_target(service_t, local_tcp_ser } void local_tcp_server_endpoint_impl::add_connection(const client_t& _client, const std::shared_ptr& _connection) { - std::scoped_lock its_lock{connections_mutex_}; - auto find_connection = connections_.find(_client); - if (find_connection != connections_.end()) { - VSOMEIP_WARNING << "Replacing already existing connection to client " << std::hex << _client - << ", previous connection: " << connections_[_client] << ", new connection " << _connection << ", endpoint > " - << this; + connection::ptr its_old_connection; + { + std::scoped_lock its_lock{connections_mutex_}; + if (auto its_itr = connections_.find(_client); its_itr != connections_.end()) { + // save it to call connection stop and destructor outside of connections_mutex_ + its_old_connection = its_itr->second; + VSOMEIP_WARNING << "Replacing already existing connection to client " << std::hex << _client + << ", previous connection: " << connections_[_client] << ", new connection " << _connection << ", endpoint > " + << this; + } + + // save new connection + connections_[_client] = _connection; } - connections_[_client] = _connection; + if (its_old_connection) { + its_old_connection->stop(); + its_old_connection.reset(); + } } -void local_tcp_server_endpoint_impl::remove_connection(const client_t& _client) { - std::scoped_lock its_lock{connections_mutex_}; - if (!connections_.erase(_client)) { - VSOMEIP_WARNING << "Client " << std::hex << _client << " has no registered connection to " - << " remove, endpoint > " << this; +void local_tcp_server_endpoint_impl::remove_connection(const client_t& _client, connection* _connection) { + connection::ptr its_old_connection; + { + std::scoped_lock its_lock{connections_mutex_}; + if (auto its_itr = connections_.find(_client); its_itr != connections_.end() && its_itr->second.get() == _connection) { + // save it to call connection stop and destructor outside of connections_mutex_ + its_old_connection = its_itr->second; + if (!connections_.erase(_client)) { + VSOMEIP_WARNING << "ltsei::" << __func__ << ": client " << std::hex << _client << " has no registered connection to " + << " remove, endpoint > " << this; + } + // if client still has a connection but a different one + } else if (its_itr != connections_.end() && its_itr->second.get() != _connection) { + VSOMEIP_WARNING << "ltsei::" << __func__ << ": tried to remove old connection " << _connection << " for client " << std::hex + << std::setw(4) << _client << " new connection: " << connections_[_client]; + its_old_connection = _connection->shared_from_this(); + } + } + + if (its_old_connection) { + its_old_connection->stop(); + its_old_connection.reset(); } } @@ -367,7 +392,7 @@ std::unique_lock local_tcp_server_endpoint_impl::connection::get_soc } void local_tcp_server_endpoint_impl::connection::start() { - std::scoped_lock its_lock{socket_mutex_}; + std::unique_lock its_lock{socket_mutex_}; if (socket_->is_open()) { const std::size_t its_capacity(recv_buffer_.capacity()); if (recv_buffer_size_ > its_capacity) { @@ -400,6 +425,7 @@ void local_tcp_server_endpoint_impl::connection::start() { shrink_count_ = 0; } } catch (const std::exception& e) { + its_lock.unlock(); handle_recv_buffer_exception(e); // don't start receiving again return; @@ -536,6 +562,16 @@ void local_tcp_server_endpoint_impl::connection::receive_cbk(boost::system::erro std::uint32_t its_command_size = 0; if (!_error && 0 < _bytes) { + +#if defined(__linux__) + // set TCP QUICKACK + // necessary, because local connections are TCP RST'd, and in order to guarantee no + // loss of data, there is (active!) waiting for TCP ACKs - which relies on speedy delivery of TCP ACKs + if (!socket_->set_quick_ack()) { + VSOMEIP_WARNING << "ltsei::receive_cbk: could not setsockopt(TCP_QUICKACK), errno " << errno; + } +#endif + #if 0 std::stringstream msg; msg << "lse::c<" << this << ">rcb: "; @@ -686,7 +722,7 @@ void local_tcp_server_endpoint_impl::connection::receive_cbk(boost::system::erro << old_client << " removed due to new client " << std::hex << std::setfill('0') << std::setw(4) << its_client << " @ " << its_address.to_string() + ":" << its_guest_port; - its_host->remove_local(old_client, true); + its_host->remove_local(old_client, true, true); } its_host->add_guest(its_client, its_address, its_guest_port); @@ -746,7 +782,7 @@ void local_tcp_server_endpoint_impl::connection::receive_cbk(boost::system::erro shutdown_and_close(true); if (bound_client_ != VSOMEIP_CLIENT_UNSET) { - its_server->remove_connection(bound_client_); + its_server->remove_connection(bound_client_, this); its_server->configuration_->get_policy_manager()->remove_client_to_sec_client_mapping(bound_client_); } } else { @@ -835,7 +871,7 @@ void local_tcp_server_endpoint_impl::connection::handle_recv_buffer_exception(co if (bound_client_ != VSOMEIP_CLIENT_UNSET) { std::shared_ptr its_server = server_.lock(); if (its_server) { - its_server->remove_connection(bound_client_); + its_server->remove_connection(bound_client_, this); } } } @@ -844,49 +880,47 @@ std::size_t local_tcp_server_endpoint_impl::connection::get_recv_buffer_capacity return recv_buffer_.capacity(); } -void local_tcp_server_endpoint_impl::connection::shutdown_and_close(bool _is_error) { +void local_tcp_server_endpoint_impl::connection::shutdown_and_close(bool _due_to_error) { #if defined(__linux__) || defined(__QNX__) boost::system::error_code its_error; io_control_operation send_buffer_size_cmd(TIOCOUTQ); std::uint32_t retry_count(0); - if (!_is_error) { - while (true) { - { - std::scoped_lock its_lock(socket_mutex_); // Do not block this mutex while waiting, to let other operations finish - if (socket_ && socket_->is_open()) { - socket_->io_control(send_buffer_size_cmd, its_error); - } + while (true) { + { + std::scoped_lock its_lock(socket_mutex_); // do not block this mutex while waiting, to let other operations finish + if (socket_ && socket_->is_open()) { + socket_->io_control(send_buffer_size_cmd, its_error); } + } - if (its_error) { - VSOMEIP_WARNING << "ltsei::" << __func__ << ": fail to read send_buffer_size " - << "(" << its_error.value() << "): " << its_error.message() << ", endpoint > " << this; + if (its_error) { + VSOMEIP_WARNING << "ltsei::" << __func__ << ": fail to read send_buffer_size " + << "(" << its_error.value() << "): " << its_error.message() << ", endpoint > " << this; + break; + } + const auto send_buffer_size = send_buffer_size_cmd.get(); + if (send_buffer_size > 0) { + // shutdown_and_close_socket was called on error, do not wait to send remaining data + if (_due_to_error) { + VSOMEIP_WARNING << "ltsei::" << __func__ << ": dropping " << send_buffer_size << " bytes on error close, endpoint > " + << this; break; - } - const auto send_buffer_size = send_buffer_size_cmd.get(); - if (send_buffer_size > 0) { - // shutdown_and_close_socket was called on error, do not wait to send remaining data - if (_is_error) { - VSOMEIP_WARNING << "ltsei::" << __func__ << ": dropping " << send_buffer_size << " bytes on error close, endpoint > " - << this; - break; - } else { - VSOMEIP_WARNING << "ltsei::" << __func__ << ": waiting[" << retry_count << "] on close to send " << send_buffer_size - << " bytes " - << ", endpoint > " << this; - std::this_thread::sleep_for(std::chrono::milliseconds(VSOMEIP_TCP_CLOSE_SEND_BUFFER_CHECK_PERIOD)); - } - } else { - break; - } - ++retry_count; - if (retry_count > VSOMEIP_TCP_CLOSE_SEND_BUFFER_RETRIES) { - VSOMEIP_ERROR << "ltsei::" << __func__ << ": max retries reached to send! will drop " << send_buffer_size - << " bytes on close, endpoint > " << this; - break; + VSOMEIP_WARNING << "ltsei::" << __func__ << ": waiting[" << retry_count << "] on close to send " << send_buffer_size + << " bytes " + << ", endpoint > " << this; + std::this_thread::sleep_for(std::chrono::milliseconds(VSOMEIP_TCP_CLOSE_SEND_BUFFER_CHECK_PERIOD)); } + + } else { + break; + } + ++retry_count; + if (retry_count > VSOMEIP_TCP_CLOSE_SEND_BUFFER_RETRIES) { + VSOMEIP_ERROR << "ltsei::" << __func__ << ": max retries reached to send! will drop " << send_buffer_size + << " bytes on close, endpoint > " << this; + break; } } #endif diff --git a/implementation/endpoints/src/local_uds_client_endpoint_impl.cpp b/implementation/endpoints/src/local_uds_client_endpoint_impl.cpp index c71667ef2..8762ab729 100644 --- a/implementation/endpoints/src/local_uds_client_endpoint_impl.cpp +++ b/implementation/endpoints/src/local_uds_client_endpoint_impl.cpp @@ -32,8 +32,6 @@ local_uds_client_endpoint_impl::local_uds_client_endpoint_impl(const std::shared // because we have no bind for local endpoints! recv_buffer_(VSOMEIP_LOCAL_CLIENT_ENDPOINT_RECV_BUFFER_SIZE, 0) { - is_supporting_magic_cookies_ = false; - this->max_message_size_ = _configuration->get_max_message_size_local(); this->queue_limit_ = _configuration->get_endpoint_queue_limit_local(); } @@ -73,7 +71,7 @@ void local_uds_client_endpoint_impl::start() { } } -void local_uds_client_endpoint_impl::stop() { +void local_uds_client_endpoint_impl::stop(bool _due_to_error) { { std::lock_guard its_lock(mutex_); sending_blocked_ = true; @@ -90,22 +88,31 @@ void local_uds_client_endpoint_impl::stop() { is_open = socket_->is_open(); } if (is_open) { - bool send_queue_empty(false); std::uint32_t times_slept(0); + std::size_t queue_size(0); while (times_slept <= LOCAL_UDS_WAIT_SEND_QUEUE_ON_STOP) { mutex_.lock(); - send_queue_empty = (queue_.size() == 0); + queue_size = queue_.size(); mutex_.unlock(); - if (send_queue_empty) { + if (queue_size == 0 || _due_to_error) { break; } else { std::this_thread::sleep_for(std::chrono::milliseconds(10)); times_slept++; } } + + if (queue_size != 0) { + if (_due_to_error) { + VSOMEIP_WARNING << "lucei::" << __func__ << ": stopping with " << queue_size << " bytes in queue, endpoint > " << this; + } else { + VSOMEIP_ERROR << "lucei::" << __func__ << ": stopping with " << queue_size << " bytes in queue, endpoint > " << this; + } + } } - shutdown_and_close_socket(false); + + shutdown_and_close_socket(false, _due_to_error); } void local_uds_client_endpoint_impl::connect() { @@ -244,8 +251,6 @@ void local_uds_client_endpoint_impl::get_configured_times_from_endpoint(service_ VSOMEIP_ERROR << "local_client_endpoint_impl::get_configured_times_from_endpoint called."; } -void local_uds_client_endpoint_impl::send_magic_cookie() { } - void local_uds_client_endpoint_impl::receive_cbk(boost::system::error_code const& _error, std::size_t _bytes) { if (_error) { diff --git a/implementation/endpoints/src/local_uds_server_endpoint_impl.cpp b/implementation/endpoints/src/local_uds_server_endpoint_impl.cpp index 23021b331..17ac6e7fa 100644 --- a/implementation/endpoints/src/local_uds_server_endpoint_impl.cpp +++ b/implementation/endpoints/src/local_uds_server_endpoint_impl.cpp @@ -35,7 +35,6 @@ local_uds_server_endpoint_impl::local_uds_server_endpoint_impl(const std::shared bool _is_routing_endpoint) : local_uds_server_endpoint_base_impl(_endpoint_host, _routing_host, _io, _configuration), acceptor_(_io), buffer_shrink_threshold_(_configuration->get_buffer_shrink_threshold()), is_routing_endpoint_(_is_routing_endpoint) { - is_supporting_magic_cookies_ = false; this->max_message_size_ = _configuration->get_max_message_size_local(); this->queue_limit_ = _configuration->get_endpoint_queue_limit_local(); @@ -114,9 +113,8 @@ void local_uds_server_endpoint_impl::start() { } } -void local_uds_server_endpoint_impl::stop() { +void local_uds_server_endpoint_impl::stop(bool /*_due_to_error*/) { - server_endpoint_impl::stop(); { std::scoped_lock its_lock{acceptor_mutex_}; if (acceptor_.is_open()) { diff --git a/implementation/endpoints/src/server_endpoint_impl.cpp b/implementation/endpoints/src/server_endpoint_impl.cpp index b4fc61a69..b05369488 100644 --- a/implementation/endpoints/src/server_endpoint_impl.cpp +++ b/implementation/endpoints/src/server_endpoint_impl.cpp @@ -101,7 +101,7 @@ void server_endpoint_impl::prepare_stop(const endpoint::prepare_stop_h } template -void server_endpoint_impl::stop() { } +void server_endpoint_impl::stop(bool /*_due_to_error*/) { } template bool server_endpoint_impl::is_client() const { @@ -113,7 +113,7 @@ void server_endpoint_impl::restart(bool _force) { (void)_force; boost::system::error_code its_error; - this->stop(); + this->stop(false); this->init(server_endpoint_impl::local_, its_error); this->start(); } diff --git a/implementation/endpoints/src/tcp_client_endpoint_impl.cpp b/implementation/endpoints/src/tcp_client_endpoint_impl.cpp index 57024f03c..e6c1b2572 100644 --- a/implementation/endpoints/src/tcp_client_endpoint_impl.cpp +++ b/implementation/endpoints/src/tcp_client_endpoint_impl.cpp @@ -25,18 +25,17 @@ namespace vsomeip_v3 { tcp_client_endpoint_impl::tcp_client_endpoint_impl(const std::shared_ptr& _endpoint_host, const std::shared_ptr& _routing_host, const endpoint_type& _local, const endpoint_type& _remote, boost::asio::io_context& _io, - const std::shared_ptr& _configuration) : + const std::shared_ptr& _configuration, bool _use_magic_cookies) : tcp_client_endpoint_base_impl(_endpoint_host, _routing_host, _local, _remote, _io, _configuration), + use_magic_cookies_(_use_magic_cookies), last_cookie_sent_(std::chrono::steady_clock::now() - std::chrono::seconds(11)), recv_buffer_size_initial_(VSOMEIP_SOMEIP_HEADER_SIZE), recv_buffer_(std::make_shared(recv_buffer_size_initial_, 0)), shrink_count_(0), buffer_shrink_threshold_(configuration_->get_buffer_shrink_threshold()), remote_address_(_remote.address()), - remote_port_(_remote.port()), last_cookie_sent_(std::chrono::steady_clock::now() - std::chrono::seconds(11)), + remote_port_(_remote.port()), // send timeout after 2/3 of configured ttl, warning after 1/3 send_timeout_(configuration_->get_sd_ttl() * 666), send_timeout_warning_(send_timeout_ / 2), tcp_restart_aborts_max_(configuration_->get_max_tcp_restart_aborts()), tcp_connect_time_max_(configuration_->get_max_tcp_connect_time()), aborted_restart_count_(0), sent_timer_(_io) { - is_supporting_magic_cookies_ = true; - this->max_message_size_ = _configuration->get_max_message_size_reliable(_remote.address().to_string(), _remote.port()); this->queue_limit_ = _configuration->get_endpoint_queue_limit(_remote.address().to_string(), _remote.port()); } @@ -45,7 +44,7 @@ tcp_client_endpoint_impl::~tcp_client_endpoint_impl() { // ensure socket close() before boost destructor // otherwise boost asio removes linger, which may leave connection in TIME_WAIT VSOMEIP_INFO << "tcei::~tcei: endpoint > " << this << " state_ > " << to_string(state_.load()); - shutdown_and_close_socket(false); + shutdown_and_close_socket(false, true); std::shared_ptr its_host = endpoint_host_.lock(); if (its_host) { @@ -326,11 +325,13 @@ void tcp_client_endpoint_impl::receive(message_buffer_ptr_t _recv_buffer, std::s } void tcp_client_endpoint_impl::send_queued(std::pair& _entry) { + std::scoped_lock its_lock{socket_mutex_}; + const service_t its_service = bithelper::read_uint16_be(&(*_entry.first)[VSOMEIP_SERVICE_POS_MIN]); const method_t its_method = bithelper::read_uint16_be(&(*_entry.first)[VSOMEIP_METHOD_POS_MIN]); const client_t its_client = bithelper::read_uint16_be(&(*_entry.first)[VSOMEIP_CLIENT_POS_MIN]); const session_t its_session = bithelper::read_uint16_be(&(*_entry.first)[VSOMEIP_SESSION_POS_MIN]); - if (has_enabled_magic_cookies_) { + if (use_magic_cookies_) { const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(now - last_cookie_sent_) > std::chrono::milliseconds(10000)) { send_magic_cookie(_entry.first); @@ -348,7 +349,6 @@ void tcp_client_endpoint_impl::send_queued(std::pairis_open()) { socket_->async_write( boost::asio::buffer(*_entry.first), @@ -483,9 +483,9 @@ void tcp_client_endpoint_impl::receive_cbk(boost::system::error_code const& _err if (has_full_message) { bool needs_forwarding(true); if (is_magic_cookie(_recv_buffer, its_iteration_gap)) { - has_enabled_magic_cookies_ = true; + use_magic_cookies_ = true; } else { - if (has_enabled_magic_cookies_) { + if (use_magic_cookies_) { uint32_t its_offset = find_magic_cookie(&(*_recv_buffer)[its_iteration_gap], static_cast(_recv_buffer_size)); if (its_offset < current_message_size) { @@ -496,7 +496,7 @@ void tcp_client_endpoint_impl::receive_cbk(boost::system::error_code const& _err } } if (needs_forwarding) { - if (!has_enabled_magic_cookies_) { + if (!use_magic_cookies_) { its_lock.unlock(); its_host->on_message(&(*_recv_buffer)[its_iteration_gap], current_message_size, this, false, VSOMEIP_ROUTING_CLIENT, nullptr, remote_address_, remote_port_); @@ -515,7 +515,7 @@ void tcp_client_endpoint_impl::receive_cbk(boost::system::error_code const& _err _recv_buffer_size -= current_message_size; its_iteration_gap += current_message_size; its_missing_capacity = 0; - } else if (has_enabled_magic_cookies_ && _recv_buffer_size > 0) { + } else if (use_magic_cookies_ && _recv_buffer_size > 0) { const uint32_t its_offset = find_magic_cookie(&(*_recv_buffer)[its_iteration_gap], _recv_buffer_size); if (its_offset < _recv_buffer_size) { _recv_buffer_size -= its_offset; @@ -574,7 +574,7 @@ void tcp_client_endpoint_impl::receive_cbk(boost::system::error_code const& _err _recv_buffer_size = 0; _recv_buffer->resize(recv_buffer_size_initial_, 0x0); _recv_buffer->shrink_to_fit(); - if (has_enabled_magic_cookies_) { + if (use_magic_cookies_) { VSOMEIP_ERROR << "Received a TCP message which exceeds " << "maximum message size (" << std::dec << current_message_size << "). Magic Cookies are enabled: " @@ -598,7 +598,7 @@ void tcp_client_endpoint_impl::receive_cbk(boost::system::error_code const& _err its_missing_capacity = current_message_size - static_cast(_recv_buffer_size); } else if (VSOMEIP_SOMEIP_HEADER_SIZE > _recv_buffer_size) { its_missing_capacity = VSOMEIP_SOMEIP_HEADER_SIZE - static_cast(_recv_buffer_size); - } else if (has_enabled_magic_cookies_ && _recv_buffer_size > 0) { + } else if (use_magic_cookies_ && _recv_buffer_size > 0) { // no need to check for magic cookie here again: has_full_message // would have been set to true if there was one present in the data _recv_buffer_size = 0; @@ -805,7 +805,7 @@ void tcp_client_endpoint_impl::send_cbk(boost::system::error_code const& _error, if (_error == boost::asio::error::operation_aborted) { // endpoint was stopped - shutdown_and_close_socket(false); + shutdown_and_close_socket(false, false); } else { if (state_ == cei_state_e::CONNECTING) { VSOMEIP_WARNING << "tce::send_cbk endpoint is already restarting:" << get_remote_information(); diff --git a/implementation/endpoints/src/tcp_server_endpoint_impl.cpp b/implementation/endpoints/src/tcp_server_endpoint_impl.cpp index e7f8afc3b..0f1875b5e 100644 --- a/implementation/endpoints/src/tcp_server_endpoint_impl.cpp +++ b/implementation/endpoints/src/tcp_server_endpoint_impl.cpp @@ -24,12 +24,11 @@ namespace vsomeip_v3 { tcp_server_endpoint_impl::tcp_server_endpoint_impl(const std::shared_ptr& _endpoint_host, const std::shared_ptr& _routing_host, boost::asio::io_context& _io, - const std::shared_ptr& _configuration) : - tcp_server_endpoint_base_impl(_endpoint_host, _routing_host, _io, _configuration), acceptor_(_io), - buffer_shrink_threshold_(configuration_->get_buffer_shrink_threshold()), + const std::shared_ptr& _configuration, bool _use_magic_cookies) : + tcp_server_endpoint_base_impl(_endpoint_host, _routing_host, _io, _configuration), use_magic_cookies_(_use_magic_cookies), + acceptor_(_io), buffer_shrink_threshold_(configuration_->get_buffer_shrink_threshold()), // send timeout after 2/3 of configured ttl, warning after 1/3 send_timeout_(configuration_->get_sd_ttl() * 666) { - is_supporting_magic_cookies_ = true; static std::atomic instance_count = 0; instance_name_ = "tsei#" + std::to_string(++instance_count) + "::"; @@ -113,7 +112,7 @@ void tcp_server_endpoint_impl::start() { if (acceptor_.is_open()) { connection::ptr new_connection = connection::create(std::dynamic_pointer_cast(shared_from_this()), max_message_size_, - buffer_shrink_threshold_, has_enabled_magic_cookies_, io_, send_timeout_); + buffer_shrink_threshold_, use_magic_cookies_, io_, send_timeout_); acceptor_.async_accept(new_connection->get_socket(), std::bind(&tcp_server_endpoint_impl::accept_cbk, @@ -126,10 +125,9 @@ void tcp_server_endpoint_impl::start() { VSOMEIP_INFO << instance_name_ << __func__ << ": done"; } -void tcp_server_endpoint_impl::stop() { +void tcp_server_endpoint_impl::stop(bool /*_due_to_error*/) { VSOMEIP_INFO << instance_name_ << __func__; - server_endpoint_impl::stop(); { std::scoped_lock first_lock(acceptor_mutex_); @@ -254,16 +252,28 @@ bool tcp_server_endpoint_impl::get_default_target(service_t, tcp_server_endpoint return false; } -void tcp_server_endpoint_impl::remove_connection(tcp_server_endpoint_impl::connection* _connection) { - std::lock_guard its_lock(connections_mutex_); - for (auto it = connections_.begin(); it != connections_.end();) { - if (it->second.get() == _connection) { - it = connections_.erase(it); - break; - } else { - ++it; +void tcp_server_endpoint_impl::remove_connection(endpoint_type _endpoint, connection* _connection) { + connection::ptr its_old_connection; + { + std::scoped_lock its_lock(connections_mutex_); + if (auto its_itr = connections_.find(_endpoint); its_itr != connections_.end() && its_itr->second.get() == _connection) { + its_old_connection = its_itr->second; + if (!connections_.erase(_endpoint)) { + VSOMEIP_WARNING << "ltsei::" << __func__ << ": remote endpoint: " << _endpoint << " has no registered connection to " + << " remove, endpoint > " << this; + } + // if client still has a connection but a different one + } else if (its_itr != connections_.end() && its_itr->second.get() != _connection) { + VSOMEIP_WARNING << "ltsei::" << __func__ << ": tried to remove old connection " << _connection << " for endpoint " << _endpoint + << " new connection: " << connections_[_endpoint]; + its_old_connection = _connection->shared_from_this(); } } + + if (its_old_connection) { + its_old_connection->stop(); + its_old_connection.reset(); + } } void tcp_server_endpoint_impl::accept_cbk(connection::ptr _connection, boost::system::error_code const& _error) { @@ -372,11 +382,11 @@ void tcp_server_endpoint_impl::disconnect_from(const client_t) { /////////////////////////////////////////////////////////////////////////////// tcp_server_endpoint_impl::connection::connection(const std::weak_ptr& _server, std::uint32_t _max_message_size, std::uint32_t _recv_buffer_size_initial, std::uint32_t _buffer_shrink_threshold, - bool _magic_cookies_enabled, boost::asio::io_context& _io, + bool _use_magic_cookies, boost::asio::io_context& _io, std::chrono::milliseconds _send_timeout) : socket_(_io), server_(_server), max_message_size_(_max_message_size), recv_buffer_size_initial_(_recv_buffer_size_initial), recv_buffer_(_recv_buffer_size_initial, 0), recv_buffer_size_(0), missing_capacity_(0), shrink_count_(0), - buffer_shrink_threshold_(_buffer_shrink_threshold), remote_port_(0), magic_cookies_enabled_(_magic_cookies_enabled), + buffer_shrink_threshold_(_buffer_shrink_threshold), remote_port_(0), use_magic_cookies_(_use_magic_cookies), last_cookie_sent_(std::chrono::steady_clock::now() - std::chrono::seconds(11)), send_timeout_(_send_timeout), send_timeout_warning_(_send_timeout / 2) { auto its_server(_server.lock()); @@ -421,7 +431,7 @@ void tcp_server_endpoint_impl::connection::start() { } void tcp_server_endpoint_impl::connection::receive() { - std::lock_guard its_lock(socket_mutex_); + std::unique_lock its_lock(socket_mutex_); if (socket_.is_open()) { const std::size_t its_capacity(recv_buffer_.capacity()); if (recv_buffer_size_ > its_capacity) { @@ -457,6 +467,7 @@ void tcp_server_endpoint_impl::connection::receive() { shrink_count_ = 0; } } catch (const std::exception& e) { + its_lock.unlock(); handle_recv_buffer_exception(e); // don't start receiving again return; @@ -504,7 +515,7 @@ void tcp_server_endpoint_impl::connection::send_queued(const target_data_iterato const method_t its_method = bithelper::read_uint16_be(&(*its_buffer)[VSOMEIP_METHOD_POS_MIN]); const client_t its_client = bithelper::read_uint16_be(&(*its_buffer)[VSOMEIP_CLIENT_POS_MIN]); const session_t its_session = bithelper::read_uint16_be(&(*its_buffer)[VSOMEIP_SESSION_POS_MIN]); - if (magic_cookies_enabled_) { + if (use_magic_cookies_) { const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(now - last_cookie_sent_) > std::chrono::milliseconds(10000)) { if (send_magic_cookie(its_buffer)) { @@ -559,6 +570,7 @@ void tcp_server_endpoint_impl::connection::receive_cbk(boost::system::error_code << static_cast( recv_buffer_[i] << " "; VSOMEIP_INFO << msg.str(); #endif + std::unique_lock its_lock(socket_mutex_); std::shared_ptr its_host = its_server->routing_host_.lock(); if (its_host) { if (!_error && 0 < _bytes) { @@ -581,18 +593,16 @@ void tcp_server_endpoint_impl::connection::receive_cbk(boost::system::error_code if (has_full_message) { bool needs_forwarding(true); if (is_magic_cookie(its_iteration_gap)) { - magic_cookies_enabled_ = true; + use_magic_cookies_ = true; } else { - if (magic_cookies_enabled_) { + if (use_magic_cookies_) { uint32_t its_offset = its_server->find_magic_cookie(&recv_buffer_[its_iteration_gap], recv_buffer_size_); if (its_offset < current_message_size) { - { - std::lock_guard its_lock(socket_mutex_); - VSOMEIP_ERROR << instance_name_ << __func__ - << ": detected Magic Cookie within message data. " - "Resyncing." - << " local: " << get_address_port_local() << " remote: " << get_address_port_remote(); - } + VSOMEIP_ERROR << instance_name_ << __func__ + << ": detected Magic Cookie within message data. " + "Resyncing." + << " local: " << get_address_port_local() << " remote: " << get_address_port_remote(); + if (!is_magic_cookie(its_iteration_gap)) { auto its_endpoint_host = its_server->endpoint_host_.lock(); if (its_endpoint_host) { @@ -619,14 +629,18 @@ void tcp_server_endpoint_impl::connection::receive_cbk(boost::system::error_code its_server->clients_to_target_[to_clients_key(its_service, its_method, its_client)] = remote_; } } - if (!magic_cookies_enabled_) { + if (!use_magic_cookies_) { + its_lock.unlock(); its_host->on_message(&recv_buffer_[its_iteration_gap], current_message_size, its_server.get(), false, VSOMEIP_ROUTING_CLIENT, nullptr, remote_address_, remote_port_); + its_lock.lock(); } else { // Only call on_message without a magic cookie in front of the buffer! if (!is_magic_cookie(its_iteration_gap)) { + its_lock.unlock(); its_host->on_message(&recv_buffer_[its_iteration_gap], current_message_size, its_server.get(), false, VSOMEIP_ROUTING_CLIENT, nullptr, remote_address_, remote_port_); + its_lock.lock(); } } } @@ -634,19 +648,19 @@ void tcp_server_endpoint_impl::connection::receive_cbk(boost::system::error_code missing_capacity_ = 0; recv_buffer_size_ -= current_message_size; its_iteration_gap += current_message_size; - } else if (magic_cookies_enabled_ && recv_buffer_size_ > 0) { + } else if (use_magic_cookies_ && recv_buffer_size_ > 0) { uint32_t its_offset = its_server->find_magic_cookie(&recv_buffer_[its_iteration_gap], recv_buffer_size_); if (its_offset < recv_buffer_size_) { - { - std::lock_guard its_lock(socket_mutex_); - VSOMEIP_ERROR << instance_name_ << __func__ << ": detected Magic Cookie within message data. Resyncing." - << " local: " << get_address_port_local() << " remote: " << get_address_port_remote(); - } + VSOMEIP_ERROR << instance_name_ << __func__ << ": detected Magic Cookie within message data. Resyncing." + << " local: " << get_address_port_local() << " remote: " << get_address_port_remote(); + if (!is_magic_cookie(its_iteration_gap)) { auto its_endpoint_host = its_server->endpoint_host_.lock(); if (its_endpoint_host) { + its_lock.unlock(); its_endpoint_host->on_error(&recv_buffer_[its_iteration_gap], static_cast(recv_buffer_size_), its_server.get(), remote_address_, remote_port_); + its_lock.lock(); } } recv_buffer_size_ -= its_offset; @@ -655,8 +669,10 @@ void tcp_server_endpoint_impl::connection::receive_cbk(boost::system::error_code if (!is_magic_cookie(its_iteration_gap)) { auto its_endpoint_host = its_server->endpoint_host_.lock(); if (its_endpoint_host) { + its_lock.unlock(); its_endpoint_host->on_error(&recv_buffer_[its_iteration_gap], static_cast(recv_buffer_size_), its_server.get(), remote_address_, remote_port_); + its_lock.lock(); } } } @@ -670,21 +686,19 @@ void tcp_server_endpoint_impl::connection::receive_cbk(boost::system::error_code || !utility::is_valid_return_code( static_cast(recv_buffer_[its_iteration_gap + VSOMEIP_RETURN_CODE_POS])))) { if (recv_buffer_[its_iteration_gap + VSOMEIP_PROTOCOL_VERSION_POS] != VSOMEIP_PROTOCOL_VERSION) { - { - std::lock_guard its_lock(socket_mutex_); - VSOMEIP_ERROR << instance_name_ << __func__ << ": wrong protocol version: 0x" << std::hex - << std::setfill('0') << std::setw(2) - << std::uint32_t(recv_buffer_[its_iteration_gap + VSOMEIP_PROTOCOL_VERSION_POS]) - << " local: " << get_address_port_local() << " remote: " << get_address_port_remote() - << ". Closing connection due to missing/broken data TCP " - "stream."; - } + VSOMEIP_ERROR << instance_name_ << __func__ << ": wrong protocol version: 0x" << std::hex << std::setfill('0') + << std::setw(2) << std::uint32_t(recv_buffer_[its_iteration_gap + VSOMEIP_PROTOCOL_VERSION_POS]) + << " local: " << get_address_port_local() << " remote: " << get_address_port_remote() + << ". Closing connection due to missing/broken data TCP " + "stream."; + // ensure to send back a error message w/ wrong protocol version + its_lock.unlock(); its_host->on_message(&recv_buffer_[its_iteration_gap], VSOMEIP_SOMEIP_HEADER_SIZE + 8, its_server.get(), false, VSOMEIP_ROUTING_CLIENT, nullptr, remote_address_, remote_port_); + its_lock.lock(); } else if (!utility::is_valid_message_type( static_cast(recv_buffer_[its_iteration_gap + VSOMEIP_MESSAGE_TYPE_POS]))) { - std::lock_guard its_lock(socket_mutex_); VSOMEIP_ERROR << instance_name_ << __func__ << ": invalid message type: 0x" << std::hex << std::setfill('0') << std::setw(2) << std::uint32_t(recv_buffer_[its_iteration_gap + VSOMEIP_MESSAGE_TYPE_POS]) << " local: " << get_address_port_local() << " remote: " << get_address_port_remote() @@ -692,35 +706,34 @@ void tcp_server_endpoint_impl::connection::receive_cbk(boost::system::error_code "stream."; } else if (!utility::is_valid_return_code( static_cast(recv_buffer_[its_iteration_gap + VSOMEIP_RETURN_CODE_POS]))) { - std::lock_guard its_lock(socket_mutex_); VSOMEIP_ERROR << instance_name_ << __func__ << ": invalid return code: 0x" << std::hex << std::setfill('0') << std::setw(2) << std::uint32_t(recv_buffer_[its_iteration_gap + VSOMEIP_RETURN_CODE_POS]) << " local: " << get_address_port_local() << " remote: " << get_address_port_remote() << ". Closing connection due to missing/broken data TCP " "stream."; } + + its_lock.unlock(); wait_until_sent(boost::asio::error::operation_aborted); return; } else if (max_message_size_ != MESSAGE_SIZE_UNLIMITED && current_message_size > max_message_size_) { recv_buffer_size_ = 0; recv_buffer_.resize(recv_buffer_size_initial_, 0x0); recv_buffer_.shrink_to_fit(); - if (magic_cookies_enabled_) { - std::lock_guard its_lock(socket_mutex_); + if (use_magic_cookies_) { VSOMEIP_ERROR << instance_name_ << __func__ << ": received a TCP message which exceeds " << "maximum message size (" << std::dec << current_message_size << " > " << std::dec << max_message_size_ << "). Magic Cookies are enabled: " << "Resetting receiver. local: " << get_address_port_local() << " remote: " << get_address_port_remote(); } else { - { - std::lock_guard its_lock(socket_mutex_); - VSOMEIP_ERROR << instance_name_ << __func__ << ": received a TCP message which exceeds " - << "maximum message size (" << std::dec << current_message_size << " > " << std::dec - << max_message_size_ << ") Magic cookies are disabled: " - << "Connection will be closed! local: " << get_address_port_local() - << " remote: " << get_address_port_remote(); - } + VSOMEIP_ERROR << instance_name_ << __func__ << ": received a TCP message which exceeds " + << "maximum message size (" << std::dec << current_message_size << " > " << std::dec + << max_message_size_ << ") Magic cookies are disabled: " + << "Connection will be closed! local: " << get_address_port_local() + << " remote: " << get_address_port_remote(); + + its_lock.unlock(); wait_until_sent(boost::asio::error::operation_aborted); return; } @@ -728,28 +741,26 @@ void tcp_server_endpoint_impl::connection::receive_cbk(boost::system::error_code missing_capacity_ = current_message_size - static_cast(recv_buffer_size_); } else if (VSOMEIP_SOMEIP_HEADER_SIZE > recv_buffer_size_) { missing_capacity_ = VSOMEIP_SOMEIP_HEADER_SIZE - static_cast(recv_buffer_size_); - } else if (magic_cookies_enabled_ && recv_buffer_size_ > 0) { + } else if (use_magic_cookies_ && recv_buffer_size_ > 0) { // no need to check for magic cookie here again: has_full_message // would have been set to true if there was one present in the data recv_buffer_size_ = 0; recv_buffer_.resize(recv_buffer_size_initial_, 0x0); recv_buffer_.shrink_to_fit(); missing_capacity_ = 0; - std::lock_guard its_lock(socket_mutex_); VSOMEIP_ERROR << instance_name_ << __func__ << ": didn't find magic cookie in broken" << " data, trying to resync." << " local: " << get_address_port_local() << " remote: " << get_address_port_remote(); } else { - { - std::lock_guard its_lock(socket_mutex_); - VSOMEIP_ERROR << instance_name_ << __func__ << ": recv_buffer_size is: " << std::dec << recv_buffer_size_ - << " but couldn't read " - "out message_size. recv_buffer_capacity: " - << recv_buffer_.capacity() << " its_iteration_gap: " << its_iteration_gap - << "local: " << get_address_port_local() << " remote: " << get_address_port_remote() - << ". Closing connection due to missing/broken data TCP " - "stream."; - } + VSOMEIP_ERROR << instance_name_ << __func__ << ": recv_buffer_size is: " << std::dec << recv_buffer_size_ + << " but couldn't read " + "out message_size. recv_buffer_capacity: " + << recv_buffer_.capacity() << " its_iteration_gap: " << its_iteration_gap + << "local: " << get_address_port_local() << " remote: " << get_address_port_remote() + << ". Closing connection due to missing/broken data TCP " + "stream."; + + its_lock.unlock(); wait_until_sent(boost::asio::error::operation_aborted); return; } @@ -765,15 +776,18 @@ void tcp_server_endpoint_impl::connection::receive_cbk(boost::system::error_code missing_capacity_ = 0; } } + + its_lock.unlock(); receive(); } } if (_error == boost::asio::error::eof || _error == boost::asio::error::connection_reset || _error == boost::asio::error::timed_out) { if (_error == boost::asio::error::timed_out) { - std::scoped_lock its_lock{socket_mutex_}; VSOMEIP_WARNING << instance_name_ << __func__ << ": " << _error.message() << " local: " << get_address_port_local() << " remote: " << get_address_port_remote(); } + + its_lock.unlock(); wait_until_sent(boost::asio::error::operation_aborted); } } @@ -852,7 +866,7 @@ void tcp_server_endpoint_impl::connection::handle_recv_buffer_exception(const st } std::shared_ptr its_server = server_.lock(); if (its_server) { - its_server->remove_connection(this); + its_server->remove_connection(remote_, this); } } @@ -905,7 +919,7 @@ void tcp_server_endpoint_impl::connection::stop_and_remove_connection() { std::lock_guard its_lock(its_server->connections_mutex_); stop(); } - its_server->remove_connection(this); + its_server->remove_connection(remote_, this); } // Dummies @@ -976,7 +990,7 @@ void tcp_server_endpoint_impl::connection::wait_until_sent(const boost::system:: std::lock_guard its_lock_inner(its_server->connections_mutex_); stop(); } - its_server->remove_connection(this); + its_server->remove_connection(remote_, this); } } // namespace vsomeip_v3 diff --git a/implementation/endpoints/src/timer.cpp b/implementation/endpoints/src/timer.cpp new file mode 100644 index 000000000..b81f64a3c --- /dev/null +++ b/implementation/endpoints/src/timer.cpp @@ -0,0 +1,121 @@ +// Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "../include/timer.hpp" +#include "../include/abstract_socket_factory.hpp" +#include "../../utility/include/is_value.hpp" + +#include + +#include +#include + +namespace vsomeip_v3 { + +timer::timer([[maybe_unused]] hidden _h, std::unique_ptr _timer, std::chrono::milliseconds _interval, task_t _task) : + timer_(std::move(_timer)), interval_(_interval), task_(std::move(_task)) { } + +timer::~timer() { + stop(); +} + +std::shared_ptr timer::create(boost::asio::io_context& _io, std::chrono::milliseconds _interval, task_t _task) { + return std::make_shared(hidden{}, abstract_socket_factory::get()->create_timer(_io), _interval, std::move(_task)); +} + +void timer::start() { + std::scoped_lock lock{mtx_}; + if (is_value(state_).none_of(state_e::STOPPED, state_e::IN_TASK_STOPPED)) { + // the timer is already running -> restart it + stop_unlocked(); + } + if (is_value(state_).any_of(state_e::IN_TASK, state_e::IN_TASK_STARTED, state_e::IN_TASK_STOPPED)) { + // the timer is currently executing the task + // -> adjust the state but don't do anything, as it will be restarted after being done with + // the task + state_ = state_e::IN_TASK_STARTED; + return; + } + start_unlocked(); +} + +void timer::stop() { + std::scoped_lock lock{mtx_}; + if (is_value(state_).any_of(state_e::STOPPED, state_e::IN_TASK_STOPPED)) { + return; + } + stop_unlocked(); +} + +[[nodiscard]] bool timer::is_running() const { + std::scoped_lock lock{mtx_}; + return is_value(state_).none_of(state_e::STOPPED, state_e::IN_TASK_STOPPED); +} + +[[nodiscard]] bool timer::set_interval(std::chrono::milliseconds _interval) { + std::scoped_lock lock{mtx_}; + if (state_ != state_e::STOPPED) { + return false; + } + interval_ = _interval; + return true; +} + +[[nodiscard]] bool timer::set_task(task_t _task) { + std::scoped_lock lock{mtx_}; + if (state_ != state_e::STOPPED) { + return false; + } + task_ = std::move(_task); + return true; +} + +void timer::stop_unlocked() { + VSOMEIP_DEBUG << "timer::" << __func__ << ": stopped: " << this; + if (is_value(state_).any_of(state_e::IN_TASK_STARTED, state_e::IN_TASK)) { + state_ = state_e::IN_TASK_STOPPED; + return; + } + state_ = state_e::STOPPED; + timer_->cancel(); +} + +void timer::start_unlocked() { + VSOMEIP_DEBUG << "timer::" << __func__ << ": started: " << this; + state_ = state_e::STARTED; + try { + timer_->expires_after(interval_); + timer_->async_wait([weak_self = weak_from_this()](auto const& _ec) { + if (auto self = weak_self.lock(); self) { + self->handle(_ec); + } + }); + } catch (std::exception const& _e) { + VSOMEIP_ERROR << "timer::" << __func__ << ": ERROR: can not start timer due to: " << _e.what(); + } +} + +void timer::handle(boost::system::error_code const& _ec) { + if (_ec == boost::asio::error::operation_aborted) { + return; + } + std::unique_lock lock{mtx_}; + if (state_ != state_e::STARTED) { + VSOMEIP_DEBUG << "timer::" << __func__ << ": not executing the task: " << this; + return; + } + state_ = state_e::IN_TASK; + lock.unlock(); + VSOMEIP_DEBUG << "timer::" << __func__ << ": start task: " << this; + bool const restart = task_(); + VSOMEIP_DEBUG << "timer::" << __func__ << ": end task: " << this; + lock.lock(); + if (state_ == state_e::IN_TASK_STARTED || (restart && state_ == state_e::IN_TASK)) { + start_unlocked(); + return; + } + state_ = state_e::STOPPED; +} +} diff --git a/implementation/endpoints/src/udp_client_endpoint_impl.cpp b/implementation/endpoints/src/udp_client_endpoint_impl.cpp index 58e807b12..c055a59a2 100644 --- a/implementation/endpoints/src/udp_client_endpoint_impl.cpp +++ b/implementation/endpoints/src/udp_client_endpoint_impl.cpp @@ -191,7 +191,7 @@ void udp_client_endpoint_impl::restart(bool _force) { is_sending_ = false; reconnect_counter_ = 0; VSOMEIP_WARNING << "uce::restart: local: " << local << " remote: " << get_address_port_remote(); - shutdown_and_close_socket(false); + shutdown_and_close_socket(false, false); state_ = cei_state_e::CONNECTING; start_connect_timer(); } @@ -454,7 +454,7 @@ void udp_client_endpoint_impl::send_cbk(boost::system::error_code const& _error, print_status(); } was_not_connected_ = true; - shutdown_and_close_socket(true); + shutdown_and_close_socket(true, true); boost::asio::dispatch(strand_, std::bind(&client_endpoint_impl::connect, this->shared_from_this())); } else if (_error == boost::asio::error::not_connected || _error == boost::asio::error::bad_descriptor || _error == boost::asio::error::no_permission) { @@ -466,18 +466,18 @@ void udp_client_endpoint_impl::send_cbk(boost::system::error_code const& _error, queue_size_ = 0; } was_not_connected_ = true; - shutdown_and_close_socket(true); + shutdown_and_close_socket(true, true); boost::asio::dispatch(strand_, std::bind(&client_endpoint_impl::connect, this->shared_from_this())); } else if (_error == boost::asio::error::operation_aborted) { VSOMEIP_WARNING << "uce::send_cbk received error: " << _error.message(); // endpoint was stopped sending_blocked_ = true; - shutdown_and_close_socket(false); + shutdown_and_close_socket(false, false); } else { if (state_ == cei_state_e::CONNECTING) { VSOMEIP_WARNING << "uce::send_cbk endpoint is already restarting:" << get_remote_information(); } else { - shutdown_and_close_socket(false); + shutdown_and_close_socket(false, true); std::shared_ptr its_host = endpoint_host_.lock(); if (its_host) { its_host->on_disconnect(shared_from_this()); diff --git a/implementation/endpoints/src/udp_server_endpoint_impl.cpp b/implementation/endpoints/src/udp_server_endpoint_impl.cpp index e886e3786..e422c95d3 100644 --- a/implementation/endpoints/src/udp_server_endpoint_impl.cpp +++ b/implementation/endpoints/src/udp_server_endpoint_impl.cpp @@ -196,7 +196,7 @@ void udp_server_endpoint_impl::start() { VSOMEIP_INFO << instance_name_ << __func__ << ": done, lifecycle_idx=" << lifecycle_idx_.load(); } -void udp_server_endpoint_impl::stop() { +void udp_server_endpoint_impl::stop(bool /*_due_to_error*/) { VSOMEIP_INFO << instance_name_ << __func__ << ": lifecycle_idx=" << lifecycle_idx_.load(); std::scoped_lock its_lock(sync_); stop_unlocked(); @@ -266,7 +266,6 @@ void udp_server_endpoint_impl::stop_unlocked() { lifecycle_idx_ += 1; is_stopped_ = true; - server_endpoint_impl::stop(); unicast_socket_.reset(); multicast_socket_.reset(); tp_reassembler_->stop(); diff --git a/implementation/endpoints/src/virtual_server_endpoint_impl.cpp b/implementation/endpoints/src/virtual_server_endpoint_impl.cpp index defb06350..e04e78343 100644 --- a/implementation/endpoints/src/virtual_server_endpoint_impl.cpp +++ b/implementation/endpoints/src/virtual_server_endpoint_impl.cpp @@ -28,7 +28,7 @@ void virtual_server_endpoint_impl::prepare_stop(const endpoint::prepare_stop_han boost::asio::post(io_, [ptr, _handler]() { _handler(ptr); }); } -void virtual_server_endpoint_impl::stop() { } +void virtual_server_endpoint_impl::stop(bool /*_due_to_error*/) { } bool virtual_server_endpoint_impl::is_established() const { return false; @@ -66,8 +66,6 @@ bool virtual_server_endpoint_impl::send_error(const std::shared_ptr>& _subscribed_eventgroups, - bool _remove_sec_client); + bool _remove_sec_client, bool _remove_due_to_error); std::set> find_eventgroups(service_t _service, instance_t _instance) const; diff --git a/implementation/routing/include/routing_manager_impl.hpp b/implementation/routing/include/routing_manager_impl.hpp index d59d2e427..34bab3dc6 100644 --- a/implementation/routing/include/routing_manager_impl.hpp +++ b/implementation/routing/include/routing_manager_impl.hpp @@ -115,7 +115,7 @@ class routing_manager_impl : public routing_manager_base, public routing_manager std::shared_ptr find_or_create_remote_client(service_t _service, instance_t _instance, bool _reliable); - void remove_local(client_t _client, bool _remove_uid); + void remove_local(client_t _client, bool _remove_uid, bool _remove_due_to_error); void on_stop_offer_service(client_t _client, service_t _service, instance_t _instance, major_version_t _major, minor_version_t _minor); void on_availability(service_t _service, instance_t _instance, availability_state_e _state, major_version_t _major, @@ -161,10 +161,10 @@ class routing_manager_impl : public routing_manager_base, public routing_manager void expire_subscriptions(const boost::asio::ip::address& _address); void expire_subscriptions(const boost::asio::ip::address& _address, std::uint16_t _port, bool _reliable); - void expire_subscriptions(const boost::asio::ip::address& _address, const configuration::port_range_t& _range, bool _reliable); + void expire_subscriptions(const boost::asio::ip::address& _address, const port_range_t& _range, bool _reliable); void expire_services(const boost::asio::ip::address& _address); void expire_services(const boost::asio::ip::address& _address, std::uint16_t _port, bool _reliable); - void expire_services(const boost::asio::ip::address& _address, const configuration::port_range_t& _range, bool _reliable); + void expire_services(const boost::asio::ip::address& _address, const port_range_t& _range, bool _reliable); std::chrono::steady_clock::time_point expire_subscriptions(bool _force); @@ -190,7 +190,7 @@ class routing_manager_impl : public routing_manager_base, public routing_manager void send_error(return_code_e _return_code, const byte_t* _data, length_t _size, instance_t _instance, bool _reliable, endpoint* const _receiver, const boost::asio::ip::address& _remote_address, std::uint16_t _remote_port); void service_endpoint_connected(service_t _service, instance_t _instance, major_version_t _major, minor_version_t _minor, - const std::shared_ptr& _endpoint, bool _unreliable_only); + const std::shared_ptr& _endpoint); void service_endpoint_disconnected(service_t _service, instance_t _instance, major_version_t _major, minor_version_t _minor, const std::shared_ptr& _endpoint); @@ -198,7 +198,7 @@ class routing_manager_impl : public routing_manager_base, public routing_manager void register_reboot_notification_handler(const reboot_notification_handler_t& _handler) const; void register_routing_ready_handler(const routing_ready_handler_t& _handler); void register_routing_state_handler(const routing_state_handler_t& _handler); - void sd_acceptance_enabled(const boost::asio::ip::address& _address, const configuration::port_range_t& _range, bool _reliable); + void sd_acceptance_enabled(const boost::asio::ip::address& _address, const port_range_t& _range, bool _reliable); void on_resend_provided_events_response(pending_remote_offer_id_t _id); client_t find_local_client(service_t _service, instance_t _instance); diff --git a/implementation/routing/include/routing_manager_stub.hpp b/implementation/routing/include/routing_manager_stub.hpp index 538d2a64c..e95d0fb15 100644 --- a/implementation/routing/include/routing_manager_stub.hpp +++ b/implementation/routing/include/routing_manager_stub.hpp @@ -106,7 +106,7 @@ class routing_manager_stub : public routing_host, public std::enable_shared_from client_t get_guest_by_address(const boost::asio::ip::address& _address, port_t _port) const override; void add_guest(client_t _client, const boost::asio::ip::address& _address, port_t _port) override; - void remove_local(client_t _client, bool _remove_sec_client) override; + void remove_local(client_t _client, bool _remove_sec_client, bool _remove_due_to_error) override; std::string get_env(client_t _client) const override; @@ -164,7 +164,7 @@ class routing_manager_stub : public routing_host, public std::enable_shared_from } inline void remove_source(client_t _source) { connection_matrix_.erase(_source); } - void remove_client_connections(client_t _client); + void remove_client_connections(client_t _client, bool _remove_due_to_error); void send_client_routing_info(const client_t _target, protocol::routing_info_entry& _entry); void send_client_routing_info(const client_t _target, std::vector&& _entries); diff --git a/implementation/routing/include/routing_manager_stub_host.hpp b/implementation/routing/include/routing_manager_stub_host.hpp index 9ccb9d14d..c0bbfdec1 100644 --- a/implementation/routing/include/routing_manager_stub_host.hpp +++ b/implementation/routing/include/routing_manager_stub_host.hpp @@ -74,7 +74,8 @@ class routing_manager_stub_host { /// This will remove all information about local client, its' offered services, and also close the client endpoint to it /// /// @param _remove_sec_client whether to also remove the security information - virtual void remove_local(client_t _client, bool _remove_sec_client) = 0; + /// @param _remove_due_to_error whether we are removing due to an error - do not bother with graceful endpoint closure + virtual void remove_local(client_t _client, bool _remove_sec_client, bool _remove_due_to_error) = 0; virtual boost::asio::io_context& get_io() = 0; virtual client_t get_client() const = 0; diff --git a/implementation/routing/src/routing_manager_base.cpp b/implementation/routing/src/routing_manager_base.cpp index faf2b48cf..a4d9285e3 100644 --- a/implementation/routing/src/routing_manager_base.cpp +++ b/implementation/routing/src/routing_manager_base.cpp @@ -1002,13 +1002,13 @@ client_t routing_manager_base::find_local_client_unlocked(service_t _service, in return its_client; } -void routing_manager_base::remove_local(client_t _client, bool _remove_uid) { - remove_local(_client, get_subscriptions(_client), _remove_uid); +void routing_manager_base::remove_local(client_t _client, bool _remove_uid, bool _remove_due_to_error) { + remove_local(_client, get_subscriptions(_client), _remove_uid, _remove_due_to_error); } void routing_manager_base::remove_local(client_t _client, const std::set>& _subscribed_eventgroups, - bool _remove_sec_client) { + bool _remove_sec_client, bool _remove_due_to_error) { vsomeip_sec_client_t its_sec_client; configuration_->get_policy_manager()->get_client_to_sec_client_mapping(_client, its_sec_client); @@ -1025,7 +1025,7 @@ void routing_manager_base::remove_local(client_t _client, << "." << std::setw(4) << its_instance << "." << std::setw(4) << its_eventgroup << "." << std::setw(4) << ANY_EVENT << "]"; } - ep_mgr_->remove_local(_client); + ep_mgr_->remove_local(_client, _remove_due_to_error); remove_known_client(_client); remove_guest(_client); { diff --git a/implementation/routing/src/routing_manager_client.cpp b/implementation/routing/src/routing_manager_client.cpp index f3e3314f3..6a475191b 100644 --- a/implementation/routing/src/routing_manager_client.cpp +++ b/implementation/routing/src/routing_manager_client.cpp @@ -156,7 +156,7 @@ void routing_manager_client::stop() { { std::scoped_lock its_receiver_lock(receiver_mutex_); if (receiver_) { - receiver_->stop(); + receiver_->stop(false); } receiver_ = nullptr; } @@ -164,7 +164,7 @@ void routing_manager_client::stop() { { std::scoped_lock its_sender_lock{sender_mutex_}; if (sender_) { - sender_->stop(); + sender_->stop(false); } // delete the sender sender_ = nullptr; @@ -172,7 +172,7 @@ void routing_manager_client::stop() { for (const auto client : ep_mgr_->get_connected_clients()) { if (client != VSOMEIP_ROUTING_CLIENT) { - remove_local(client, true); + remove_local(client, true, false); } } } @@ -1210,10 +1210,10 @@ void routing_manager_client::on_message(const byte_t* _data, length_t _size, end } VSOMEIP_INFO << "SUBSCRIBE(" << std::hex << std::setfill('0') << std::setw(4) << its_client << "): [" << std::setw(4) << its_service << "." << std::setw(4) << its_instance << "." - << std::setw(4) << its_eventgroup << ":" << std::setw(4) << its_event << ":" << std::dec - << static_cast(its_major) << "] " << std::boolalpha - << (its_pending_id != PENDING_SUBSCRIPTION_ID) << " " - << (_subscription_accepted ? std::to_string(its_count) + " accepted" : "not accepted"); + << std::setw(4) << its_eventgroup << ":" << std::setw(4) << its_event << ":" + << static_cast(its_major) << "] " + << (_subscription_accepted ? "accepted." : "not accepted.") << " id=" << std::setw(4) + << its_pending_id << std::dec << " subscribers=" << its_count; }); } else { send_subscribe_nack(its_client, its_service, its_instance, its_eventgroup, its_event, its_pending_id); @@ -1332,8 +1332,8 @@ void routing_manager_client::on_message(const byte_t* _data, length_t _size, end } VSOMEIP_INFO << "UNSUBSCRIBE(" << std::hex << std::setfill('0') << std::setw(4) << its_client << "): [" << std::setw(4) << its_service << "." << std::setw(4) << its_instance << "." << std::setw(4) << its_eventgroup << "." - << std::setw(4) << its_event << "] " << std::boolalpha << (its_pending_id != PENDING_SUBSCRIPTION_ID) << " " - << std::dec << its_remote_subscriber_count; + << std::setw(4) << its_event << "] id=" << std::setw(4) << its_pending_id << " subscribers=" << std::dec + << its_remote_subscriber_count; } else VSOMEIP_ERROR << __func__ << ": unsubscribe command deserialization failed (" << std::dec << static_cast(its_error) << ")"; @@ -1686,7 +1686,7 @@ void routing_manager_client::on_routing_info(const byte_t* _data, uint32_t _size << its_address.to_string() + ":" << its_port; // also removes guest - remove_local(old_client, true); + remove_local(old_client, true, true); } add_guest(its_client, its_address, its_port); @@ -1780,7 +1780,7 @@ void routing_manager_client::reconnect(const std::map& _c // Remove all local connections/endpoints for (const auto& c : _clients) { if (c.first != VSOMEIP_ROUTING_CLIENT) { - remove_local(c.first, true); + remove_local(c.first, true, true); } } @@ -1795,7 +1795,7 @@ void routing_manager_client::reconnect(const std::map& _c << " to use the server endpoint due to credential check failed!"; std::scoped_lock its_sender_lock{sender_mutex_}; if (sender_) { - sender_->stop(); + sender_->stop(true); } return; } @@ -2457,7 +2457,7 @@ void routing_manager_client::handle_client_error(client_t _client) { // First ensure that the connection is dropped, before enforcing a // reconnect from the client. Otherwise a client subscribe might // be handled by a partially cleaned-up connection - remove_local(_client, true); + remove_local(_client, true, true); // Remove the client from the local connections. { std::scoped_lock lock{receiver_mutex_}; diff --git a/implementation/routing/src/routing_manager_impl.cpp b/implementation/routing/src/routing_manager_impl.cpp index 021a26fc6..be7f41ce0 100644 --- a/implementation/routing/src/routing_manager_impl.cpp +++ b/implementation/routing/src/routing_manager_impl.cpp @@ -299,7 +299,7 @@ void routing_manager_impl::stop() { for (const auto client : ep_mgr_->get_connected_clients()) { if (client != VSOMEIP_ROUTING_CLIENT) { - remove_local(client, true); + remove_local(client, true, false); } } } @@ -535,9 +535,6 @@ void routing_manager_impl::request_service(client_t _client, service_t _service, } if (_client == get_client()) { - if (stub_) - stub_->create_local_receiver(); - protocol::service its_request(_service, _instance, _major, _minor); std::set requests; requests.insert(its_request); @@ -1528,7 +1525,7 @@ void routing_manager_impl::on_stop_offer_service(client_t _client, service_t _se [this, ptr](std::shared_ptr _endpoint_to_stop) { if (ep_mgr_impl_->remove_server_endpoint(_endpoint_to_stop->get_local_port(), _endpoint_to_stop->is_reliable())) { - _endpoint_to_stop->stop(); + _endpoint_to_stop->stop(false); } }, ANY_SERVICE); @@ -2000,7 +1997,7 @@ void routing_manager_impl::init_service_info(service_t _service, instance_t _ins } } -void routing_manager_impl::remove_local(client_t _client, bool _remove_uid) { +void routing_manager_impl::remove_local(client_t _client, bool _remove_uid, bool _remove_due_to_error) { std::set> its_clients_subscriptions; its_clients_subscriptions = get_subscriptions(_client); @@ -2016,7 +2013,7 @@ void routing_manager_impl::remove_local(client_t _client, bool _remove_uid) { } unsubscribe(_client, &its_sec_client, service, instance, eventgroup, ANY_EVENT); } - routing_manager_base::remove_local(_client, its_clients_subscriptions, _remove_uid); + routing_manager_base::remove_local(_client, its_clients_subscriptions, _remove_uid, _remove_due_to_error); for (const auto& s : get_requested_services(_client)) { release_service(_client, s.service_, s.instance_); @@ -2080,7 +2077,6 @@ void routing_manager_impl::add_routing_info(service_t _service, instance_t _inst _unreliable_address, _unreliable_port, &is_unreliable_known); bool udp_inserted(false); - bool tcp_inserted(false); // Add endpoint(s) if necessary if (_reliable_port != ILLEGAL_PORT && !is_reliable_known) { std::shared_ptr endpoint_def_tcp = @@ -2090,10 +2086,8 @@ void routing_manager_impl::add_routing_info(service_t _service, instance_t _inst endpoint_definition::get(_unreliable_address, _unreliable_port, false, _service, _instance); ep_mgr_impl_->add_remote_service_info(_service, _instance, endpoint_def_tcp, endpoint_def_udp); udp_inserted = true; - tcp_inserted = true; } else { ep_mgr_impl_->add_remote_service_info(_service, _instance, endpoint_def_tcp); - tcp_inserted = true; } // check if service was requested and establish TCP connection if necessary @@ -2162,34 +2156,26 @@ void routing_manager_impl::add_routing_info(service_t _service, instance_t _inst } } } - if (!is_reliable_known && !tcp_inserted) { - // UDP only service can be marked as available instantly - if (has_requester_unlocked(_service, _instance, _major, _minor)) { - if (stub_) - stub_->on_offer_service(VSOMEIP_ROUTING_CLIENT, _service, _instance, _major, _minor); - on_availability(_service, _instance, availability_state_e::AS_AVAILABLE, _major, _minor); - } else { - on_availability(_service, _instance, availability_state_e::AS_OFFERED, _major, _minor); - } - } - if (discovery_) { - std::shared_ptr ep = its_info->get_endpoint(false); - if (ep && ep->is_established()) { - discovery_->on_endpoint_connected(_service, _instance, ep); - } - } } else if (_unreliable_port != ILLEGAL_PORT && is_unreliable_known) { std::scoped_lock its_lock_inner{requested_services_mutex_}; if (has_requester_unlocked(_service, _instance, _major, _minor)) { if (_reliable_port == ILLEGAL_PORT && !is_reliable_known && stub_ && !stub_->contained_in_routing_info(VSOMEIP_ROUTING_CLIENT, _service, _instance, its_info->get_major(), its_info->get_minor())) { - stub_->on_offer_service(VSOMEIP_ROUTING_CLIENT, _service, _instance, its_info->get_major(), its_info->get_minor()); - on_availability(_service, _instance, availability_state_e::AS_AVAILABLE, its_info->get_major(), its_info->get_minor()); - if (discovery_) { - std::shared_ptr ep = its_info->get_endpoint(false); - if (ep && ep->is_established()) { - discovery_->on_endpoint_connected(_service, _instance, ep); + std::shared_ptr ep = its_info->get_endpoint(false); + if (ep) { + if (ep->is_established()) { + stub_->on_offer_service(VSOMEIP_ROUTING_CLIENT, _service, _instance, its_info->get_major(), its_info->get_minor()); + on_availability(_service, _instance, availability_state_e::AS_AVAILABLE, its_info->get_major(), + its_info->get_minor()); + if (discovery_) { + discovery_->on_endpoint_connected(_service, _instance, ep); + } + } + } else { + ep_mgr_impl_->find_or_create_remote_client(_service, _instance, false); + for (const client_t its_client : get_requesters_unlocked(_service, _instance, _major, _minor)) { + its_info->add_client(its_client); } } } @@ -2321,18 +2307,17 @@ void routing_manager_impl::update_routing_info(std::chrono::milliseconds _elapse } void routing_manager_impl::expire_services(const boost::asio::ip::address& _address) { - expire_services(_address, configuration::port_range_t(ANY_PORT, ANY_PORT), false); + expire_services(_address, port_range_t(ANY_PORT, ANY_PORT), false); } void routing_manager_impl::expire_services(const boost::asio::ip::address& _address, std::uint16_t _port, bool _reliable) { - expire_services(_address, configuration::port_range_t(_port, _port), _reliable); + expire_services(_address, port_range_t(_port, _port), _reliable); } -void routing_manager_impl::expire_services(const boost::asio::ip::address& _address, const configuration::port_range_t& _range, - bool _reliable) { +void routing_manager_impl::expire_services(const boost::asio::ip::address& _address, const port_range_t& _range, bool _reliable) { std::map> its_expired_offers; - const bool expire_all = (_range.first == ANY_PORT && _range.second == ANY_PORT); + const bool expire_all = _range.is_any(); for (auto& s : get_services_remote()) { for (auto& i : s.second) { @@ -2343,8 +2328,7 @@ void routing_manager_impl::expire_services(const boost::asio::ip::address& _addr its_client_endpoint = std::dynamic_pointer_cast(i.second->get_endpoint(!_reliable)); } if (its_client_endpoint) { - if ((expire_all - || (its_client_endpoint->get_remote_port() >= _range.first && its_client_endpoint->get_remote_port() <= _range.second)) + if ((expire_all || _range.contains(its_client_endpoint->get_remote_port())) && its_client_endpoint->get_remote_address(its_address) && its_address == _address) { if (discovery_) { discovery_->unsubscribe_all(s.first, i.first); @@ -2359,24 +2343,25 @@ void routing_manager_impl::expire_services(const boost::asio::ip::address& _addr for (auto& i : s.second) { VSOMEIP_INFO << "rmi::" << __func__ << " for address: " << _address << " : delete service/instance " << std::hex << std::setfill('0') << std::setw(4) << s.first << "." << std::setw(4) << i << " port [" << std::dec - << _range.first << "," << _range.second << "] reliability=" << std::boolalpha << _reliable; + << _range.start_ << "," << _range.end_ << "] reliability=" << std::boolalpha << _reliable; del_routing_info(s.first, i, true, true, true); } } } void routing_manager_impl::expire_subscriptions(const boost::asio::ip::address& _address) { - expire_subscriptions(_address, configuration::port_range_t(ANY_PORT, ANY_PORT), false); + expire_subscriptions(_address, port_range_t(ANY_PORT, ANY_PORT), false); } void routing_manager_impl::expire_subscriptions(const boost::asio::ip::address& _address, std::uint16_t _port, bool _reliable) { - expire_subscriptions(_address, configuration::port_range_t(_port, _port), _reliable); + expire_subscriptions(_address, port_range_t(_port, _port), _reliable); } -void routing_manager_impl::expire_subscriptions(const boost::asio::ip::address& _address, const configuration::port_range_t& _range, - bool _reliable) { - const bool expire_all = (_range.first == ANY_PORT && _range.second == ANY_PORT); +void routing_manager_impl::expire_subscriptions(const boost::asio::ip::address& _address, const port_range_t& _range, bool _reliable) { + std::stringstream log_header; + log_header << "rmi::" << __func__ << "{remote=" << _address << "}: "; + const bool expire_all = _range.is_any(); eventgroups_t its_eventgroups; { std::scoped_lock its_lock{eventgroups_mutex_}; @@ -2386,47 +2371,34 @@ void routing_manager_impl::expire_subscriptions(const boost::asio::ip::address& for (const auto& [key, its_eventgroup] : its_eventgroups) { for (auto& [eventgroup_id, its_info] : its_eventgroup) { for (auto its_subscription : its_info->get_remote_subscriptions()) { + std::stringstream subscription_details; + subscription_details << std::hex << std::setfill('0') << " eventgroup=" << std::setw(4) << key.service() << "." + << std::setw(4) << key.instance() << "." << std::setw(4) << eventgroup_id << " id=" << std::setw(4) + << its_subscription->get_id(); if (its_subscription->is_forwarded()) { - VSOMEIP_WARNING << "rmi::" << __func__ << ": New remote subscription replaced expired [" << std::hex - << std::setfill('0') << std::setw(4) << key.service() << "." << std::setw(4) << key.instance() << "." - << std::setw(4) << eventgroup_id << "]"; + VSOMEIP_WARNING << log_header.str() << "Subscription replaced." << subscription_details.str(); continue; } - // Note: get_remote_subscription delivers a copied - // set of subscriptions. Thus, its is possible to - // to remove them within the loop. auto its_ep_definition = _reliable ? its_subscription->get_reliable() : its_subscription->get_unreliable(); + if (!its_ep_definition && expire_all) { + its_ep_definition = _reliable ? its_subscription->get_unreliable() : its_subscription->get_reliable(); + } - if (!its_ep_definition && expire_all) - its_ep_definition = (!_reliable) ? its_subscription->get_reliable() : its_subscription->get_unreliable(); - - if (its_ep_definition && its_ep_definition->get_address() == _address - && (expire_all - || (its_ep_definition->get_remote_port() >= _range.first - && its_ep_definition->get_remote_port() <= _range.second))) { - - // TODO: Check whether subscriptions to different hosts are valid. - // IF yes, we probably need to simply reset the corresponding - // endpoint instead of removing the subscription... - VSOMEIP_INFO << "rmi::" << __func__ << ": removing subscription to " << std::hex << its_info->get_service() << "." - << std::hex << its_info->get_instance() << "." << std::hex << its_info->get_eventgroup() << " from target " - << its_ep_definition->get_address() << ":" << std::dec << its_ep_definition->get_port() - << " reliable=" << std::boolalpha << its_ep_definition->is_reliable(); - if (expire_all) { - its_ep_definition = - (!its_ep_definition->is_reliable()) ? its_subscription->get_reliable() : its_subscription->get_unreliable(); - if (its_ep_definition) { - VSOMEIP_INFO << "rmi::" << __func__ << ": removing subscription to " << std::hex << its_info->get_service() - << "." << std::hex << its_info->get_instance() << "." << std::hex << its_info->get_eventgroup() - << " from target " << its_ep_definition->get_address() << ":" << std::dec - << its_ep_definition->get_port() << " reliable=" << std::boolalpha - << its_ep_definition->is_reliable(); - } + if (!its_ep_definition) { + continue; + } + + const bool in_port_range = _range.is_any() || _range.contains(its_ep_definition->get_remote_port()); + if (its_ep_definition->get_address() == _address && in_port_range) { + if (its_subscription->is_expired()) { + VSOMEIP_WARNING << log_header.str() << "Subscription already expired." << subscription_details.str(); + } else { + VSOMEIP_INFO << log_header.str() << "Removing subscription." << subscription_details.str(); + its_subscription->set_expired(); + on_remote_unsubscribe(its_subscription); } - its_subscription->set_expired(); - on_remote_unsubscribe(its_subscription); } } } @@ -2614,7 +2586,8 @@ void routing_manager_impl::on_subscribe_ack(client_t _client, service_t _service << _service << "." << std::setw(4) << _instance << "." << std::setw(4) << _eventgroup << "]" << " from " << its_subscription->get_subscriber()->get_address() << ":" << std::dec << its_subscription->get_subscriber()->get_port() - << (its_subscription->get_subscriber()->is_reliable() ? " reliable" : " unreliable") << " was accepted"; + << (its_subscription->get_subscriber()->is_reliable() ? " reliable" : " unreliable") + << " was accepted. id=" << std::setw(4) << _id; return; } @@ -2684,7 +2657,8 @@ void routing_manager_impl::on_subscribe_nack(client_t _client, service_t _servic << _service << "." << std::setw(4) << _instance << "." << std::setw(4) << _eventgroup << "]" << " from " << its_subscription->get_subscriber()->get_address() << ":" << std::dec << its_subscription->get_subscriber()->get_port() - << (its_subscription->get_subscriber()->is_reliable() ? " reliable" : " unreliable") << " was not accepted"; + << (its_subscription->get_subscriber()->is_reliable() ? " reliable" : " unreliable") + << " was not accepted. id=" << std::setw(4) << _id; } if (_remove) its_eventgroup->remove_remote_subscription(_id); @@ -2821,7 +2795,7 @@ std::chrono::steady_clock::time_point routing_manager_impl::expire_subscriptions } else { auto its_expiration = s->get_expiration(its_client); if (its_expiration != std::chrono::steady_clock::time_point()) { - if (its_expiration < now) { + if (its_expiration < now && !s->is_expired()) { its_expired_subscriptions[s].insert(its_client); } else if (its_expiration < its_next_expiration) { its_next_expiration = its_expiration; @@ -3887,8 +3861,7 @@ void routing_manager_impl::register_routing_state_handler(const routing_state_ha routing_state_handler_ = _handler; } -void routing_manager_impl::sd_acceptance_enabled(const boost::asio::ip::address& _address, const configuration::port_range_t& _range, - bool _reliable) { +void routing_manager_impl::sd_acceptance_enabled(const boost::asio::ip::address& _address, const port_range_t& _range, bool _reliable) { expire_subscriptions(_address, _range, _reliable); expire_services(_address, _range, _reliable); } @@ -3984,14 +3957,14 @@ void routing_manager_impl::on_unsubscribe_ack(client_t _client, service_t _servi } } } else { - VSOMEIP_ERROR << "rmi::" << __func__ << ": Unknown StopSubscribe " << std::dec << _id << " for eventgroup [" << std::hex - << std::setfill('0') << std::setw(4) << _service << "." << std::setw(4) << _instance << "." << std::setw(4) - << _eventgroup << "]"; + VSOMEIP_ERROR << __func__ << ": Unknown StopSubscribe for eventgroup [" << std::hex << std::setfill('0') << std::setw(4) + << _service << "." << std::setw(4) << _instance << "." << std::setw(4) << _eventgroup << "] id=" << std::setw(4) + << _id; } } else { VSOMEIP_ERROR << "rmi::" << __func__ << ": Received StopSubscribe for unknown eventgroup: (" << std::hex << std::setfill('0') << std::setw(4) << _client << "): [" << std::setw(4) << _service << "." << std::setw(4) << _instance << "." - << std::setw(4) << _eventgroup << "]"; + << _eventgroup << "] id=" << std::setw(4) << _id; } } @@ -4062,7 +4035,7 @@ void routing_manager_impl::cleanup_server_endpoint(service_t _service, const std if (ep_mgr_impl_->remove_instance(_service, _endpoint.get())) { if (ep_mgr_impl_->remove_server_endpoint(_endpoint->get_local_port(), _endpoint->is_reliable())) { // Stop endpoint (close socket) to release its async_handlers! - _endpoint->stop(); + _endpoint->stop(false); } } } @@ -4106,16 +4079,10 @@ void routing_manager_impl::print_stub_status() const { } void routing_manager_impl::service_endpoint_connected(service_t _service, instance_t _instance, major_version_t _major, - minor_version_t _minor, const std::shared_ptr& _endpoint, - bool _unreliable_only) { - - if (!_unreliable_only) { - // Mark only TCP-only and TCP+UDP services available here - // UDP-only services are already marked as available in add_routing_info - if (stub_) - stub_->on_offer_service(VSOMEIP_ROUTING_CLIENT, _service, _instance, _major, _minor); - on_availability(_service, _instance, availability_state_e::AS_AVAILABLE, _major, _minor); - } + minor_version_t _minor, const std::shared_ptr& _endpoint) { + if (stub_) + stub_->on_offer_service(VSOMEIP_ROUTING_CLIENT, _service, _instance, _major, _minor); + on_availability(_service, _instance, availability_state_e::AS_AVAILABLE, _major, _minor); auto its_timer = std::make_shared(io_); its_timer->expires_after(std::chrono::milliseconds(3)); diff --git a/implementation/routing/src/routing_manager_stub.cpp b/implementation/routing/src/routing_manager_stub.cpp index d4a4874d5..8836f3f5e 100644 --- a/implementation/routing/src/routing_manager_stub.cpp +++ b/implementation/routing/src/routing_manager_stub.cpp @@ -101,6 +101,8 @@ void routing_manager_stub::start() { root_->start(); } + create_local_receiver(); + client_registration_running_ = true; { std::scoped_lock its_thread_pool_lock(client_registration_mutex_); @@ -154,12 +156,12 @@ void routing_manager_stub::stop() { } if (root_ && !is_socket_activated_) { - root_->stop(); + root_->stop(false); root_ = nullptr; } if (local_receiver_) { - local_receiver_->stop(); + local_receiver_->stop(false); local_receiver_ = nullptr; } } @@ -380,7 +382,7 @@ void routing_manager_stub::on_message(const byte_t* _data, length_t _size, endpo VSOMEIP_INFO << "SUBSCRIBE ACK(" << std::hex << std::setfill('0') << std::setw(4) << its_client << "): [" << std::setw(4) << its_service << "." << std::setw(4) << its_instance << "." << std::setw(4) << its_eventgroup << "." - << std::setw(4) << its_notifier << "]"; + << std::setw(4) << its_notifier << "] id=" << std::setw(4) << its_subscription_id; } else VSOMEIP_ERROR << __func__ << ": deserializing subscribe ack failed (" << std::dec << static_cast(its_error) << ")"; @@ -404,7 +406,7 @@ void routing_manager_stub::on_message(const byte_t* _data, length_t _size, endpo VSOMEIP_INFO << "SUBSCRIBE NACK(" << std::hex << std::setfill('0') << std::setw(4) << its_client << "): [" << std::setw(4) << its_service << "." << std::setw(4) << its_instance << "." << std::setw(4) << its_eventgroup << "." - << std::setw(4) << its_notifier << "]"; + << std::setw(4) << its_notifier << "] id=" << std::setw(4) << its_subscription_id; } else VSOMEIP_ERROR << __func__ << ": deserializing subscribe nack failed (" << std::dec << static_cast(its_error) << ")"; @@ -425,7 +427,8 @@ void routing_manager_stub::on_message(const byte_t* _data, length_t _size, endpo host_->on_unsubscribe_ack(its_client, its_service, its_instance, its_eventgroup, its_subscription_id); VSOMEIP_INFO << "UNSUBSCRIBE ACK(" << std::hex << std::setfill('0') << std::setw(4) << its_client << "): [" << std::setw(4) - << its_service << "." << std::setw(4) << its_instance << "." << std::setw(4) << its_eventgroup << "]"; + << its_service << "." << std::setw(4) << its_instance << "." << std::setw(4) << its_eventgroup + << "] id=" << std::setw(4) << its_subscription_id; } else VSOMEIP_ERROR << __func__ << ": deserializing unsubscribe ack failed (" << std::dec << static_cast(its_error) << ")"; break; @@ -688,8 +691,8 @@ void routing_manager_stub::add_guest(client_t _client, const boost::asio::ip::ad host_->add_guest(_client, _address, _port); } -void routing_manager_stub::remove_local(client_t _client, bool _remove_sec_client) { - host_->remove_local(_client, _remove_sec_client); +void routing_manager_stub::remove_local(client_t _client, bool _remove_sec_client, bool _remove_due_to_error) { + host_->remove_local(_client, _remove_sec_client, _remove_due_to_error); } void routing_manager_stub::on_register_application(client_t _client, bool& continue_registration) { @@ -717,7 +720,7 @@ void routing_manager_stub::on_register_application(client_t _client, bool& conti #endif // !VSOMEIP_DISABLE_SECURITY if (endpoint == nullptr || !endpoint->wait_connecting_timer()) { VSOMEIP_WARNING << "Application: " << std::hex << _client << " endpoint " << endpoint << " failed to start. Removing it."; - remove_client_connections(_client); + remove_client_connections(_client, true); continue_registration = false; } } @@ -880,13 +883,13 @@ void routing_manager_stub::registration_func(client_t client_id, std::vectorget_network(), client_id); } } } -void routing_manager_stub::remove_client_connections(client_t client_id) { +void routing_manager_stub::remove_client_connections(client_t client_id, bool _remove_due_to_error) { { std::scoped_lock its_guard{routing_info_mutex_}; auto find_connections = connection_matrix_.find(client_id); @@ -906,7 +909,7 @@ void routing_manager_stub::remove_client_connections(client_t client_id) { } service_requests_.erase(client_id); } - host_->remove_local(client_id, false); + host_->remove_local(client_id, false, _remove_due_to_error); // notice that the effective shared_ptr copy is ensuring that the object // does not go out of scope during execution if (auto endpoint = std::dynamic_pointer_cast(root_)) { @@ -928,10 +931,6 @@ void routing_manager_stub::on_offer_service(client_t _client, service_t _service VSOMEIP_DEBUG << "ON_OFFER_SERVICE (" << std::hex << std::setfill('0') << std::setw(4) << _client << "): [" << std::setw(4) << _service << "." << std::setw(4) << _instance << ":" << std::dec << static_cast(_major) << "." << _minor << "]"; - if (_client == host_->get_client()) { - create_local_receiver(); - } - std::scoped_lock its_guard{routing_info_mutex_}; routing_info_[_client].second[_service][_instance] = std::make_pair(_major, _minor); if (configuration_->is_security_enabled()) { @@ -1589,8 +1588,8 @@ void routing_manager_stub::update_registration(client_t _client, registration_ty // we *definitely* need to do this in order - deregister old client, register new client // therefore schedule another registration event - // NOTE: no danger of deeper recursion, because of `DEREGISTER` falling into another branch - update_registration(old_client, registration_type_e::DEREGISTER, _address, _port); + // NOTE: no danger of deeper recursion, because of `DEREGISTER_ON_ERROR` falling into another branch + update_registration(old_client, registration_type_e::DEREGISTER_ON_ERROR, _address, _port); } host_->add_guest(_client, _address, _port); diff --git a/implementation/runtime/src/application_impl.cpp b/implementation/runtime/src/application_impl.cpp index 4385aa5fe..d2697cbc0 100644 --- a/implementation/runtime/src/application_impl.cpp +++ b/implementation/runtime/src/application_impl.cpp @@ -368,8 +368,6 @@ void application_impl::start() { const size_t io_thread_count = configuration_->get_io_thread_count(name_); const int io_thread_nice_level = configuration_->get_io_thread_nice_level(name_); - // Amount of time to run the IO context. - const size_t event_loop_periodicity = configuration_->get_event_loop_periodicity(name_); { std::scoped_lock its_lock{start_stop_mutex_}; if (io_.stopped()) { @@ -396,7 +394,7 @@ void application_impl::start() { #if defined(__linux__) || defined(__QNX__) << " I/O nice " << io_thread_nice_level #endif - << " boost event loop period " << event_loop_periodicity; + ; start_caller_id_ = std::this_thread::get_id(); { @@ -422,7 +420,7 @@ void application_impl::start() { routing_->start(); for (size_t i = 0; i < io_thread_count - 1; i++) { - auto its_thread = std::make_shared([this, i, io_thread_nice_level, event_loop_periodicity] { + auto its_thread = std::make_shared([this, i, io_thread_nice_level] { #if defined(__linux__) { std::stringstream s; @@ -441,14 +439,18 @@ void application_impl::start() { while (true) { try { - if (event_loop_periodicity) { - io_.run_for(std::chrono::seconds(event_loop_periodicity)); - } else { - io_.run(); - } - if (stopped_) { - break; + io_.run(); + + if (!stopped_) { + VSOMEIP_FATAL << "I/O context has unexpectedly exited for thread " << std::hex << std::setfill('0') + << std::setw(4) << client_ << "_io" << std::setw(2) << i + 1 << ", application '" << name_ + << "', id " << std::hex << std::this_thread::get_id() +#if defined(__linux__) + << ", tid " << std::dec << static_cast(syscall(SYS_gettid)) +#endif + << "."; } + break; } catch (const std::exception& e) { VSOMEIP_ERROR << "application_impl::start() " "caught exception: " @@ -474,6 +476,7 @@ void application_impl::start() { #endif ; + // NOTE: must happen *AFTER* `routing_->start()`, as plugins may already do offer_service auto its_plugins = configuration_->get_plugins(name_); auto its_app_plugin_info = its_plugins.find(plugin_type_e::APPLICATION_PLUGIN); if (its_app_plugin_info != its_plugins.end()) { @@ -489,17 +492,20 @@ void application_impl::start() { utility::set_thread_niceness(io_thread_nice_level); while (true) { try { - if (event_loop_periodicity) { - io_.run_for(std::chrono::seconds(event_loop_periodicity)); - } else { - io_.run(); + io_.run(); + if (!stopped_) { + VSOMEIP_FATAL << "I/O context has unexpectedly exited for thread " << std::hex << std::setfill('0') << std::setw(4) + << client_ << "_io00" + << ", application '" << name_ << "', id " << std::hex << std::this_thread::get_id() +#if defined(__linux__) + << ", tid " << std::dec << static_cast(syscall(SYS_gettid)) +#endif + ; } - if (stopped_) { - if (stop_thread_.joinable()) { - stop_thread_.join(); - } - break; + if (stop_thread_.joinable()) { + stop_thread_.join(); } + break; } catch (const std::exception& e) { VSOMEIP_ERROR << "application_impl::start() caught exception: " << e.what(); } @@ -829,6 +835,16 @@ availability_state_e application_impl::are_available_unlocked(available_t& _avai } void application_impl::send(std::shared_ptr _message) { + + // likely that the user created a message (with `runtime::create_message`) and forgot to set the type + // fail here in an obvious way, instead of down-the-line in some client-lookup code + if (_message->get_message_type() == message_type_e::MT_UNKNOWN) { + VSOMEIP_ERROR << "application_impl::" << __func__ << ": message [" << std::hex << std::setfill('0') << std::setw(4) + << _message->get_service() << "." << std::setw(4) << _message->get_instance() << "." << std::setw(4) + << _message->get_method() << "] has unknown type, cannot send!"; + return; + } + bool is_request = utility::is_request(_message); if (client_side_logging_ && (client_side_logging_filter_.empty() @@ -2614,7 +2630,7 @@ void application_impl::set_sd_acceptance_required(const remote_info_t& _remote, return; } - configuration::port_range_t its_range{_remote.first_, _remote.last_}; + port_range_t its_range{_remote.first_, _remote.last_}; configuration_->set_sd_acceptance_rule(its_address, its_range, port_type_e::PT_UNKNOWN, _path, _remote.is_reliable_, _enable, true); if (_enable && routing_) { diff --git a/implementation/utility/include/is_value.hpp b/implementation/utility/include/is_value.hpp new file mode 100644 index 000000000..153c72956 --- /dev/null +++ b/implementation/utility/include/is_value.hpp @@ -0,0 +1,30 @@ +// Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef VSOMEIP_V3_IS_VALUE_HPP_ +#define VSOMEIP_V3_IS_VALUE_HPP_ + +namespace vsomeip_v3 { + +template +struct is_value { + constexpr explicit is_value(T _t) : t_(_t) { } + + template + [[nodiscard]] bool any_of(Args... args) const&& { + return ((t_ == args) || ...); + } + + template + [[nodiscard]] bool none_of(Args... args) const&& { + return ((t_ != args) && ...); + } + + T t_; +}; + +} + +#endif diff --git a/libvsomeip.yaml b/libvsomeip.yaml index ab21f57c3..e77aa6966 100644 --- a/libvsomeip.yaml +++ b/libvsomeip.yaml @@ -1,5 +1,5 @@ - name: libvsomeip - version: 3.6.0 + version: 3.6.1 vendor: Lynx Team license: concluded: CLOSED and MPLv2 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1c719bf49..4a2111e79 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,7 +6,14 @@ ############################################################################## # Test section ############################################################################## -set(CMAKE_CXX_STANDARD 20) +if(NOT DEFINED VSOMEIP_CXX_STANDARD) + set (VSOMEIP_CXX_STANDARD 20) +endif() +if (VSOMEIP_CXX_STANDARD LESS 20) + message(STATUS "C++ standard is less than 20, force to use C++20 for tests") + set (VSOMEIP_CXX_STANDARD 20) +endif() +set(CMAKE_CXX_STANDARD ${VSOMEIP_CXX_STANDARD}) ############################################################################## # google benchmark diff --git a/test/internal_routing_disabled_acceptance_test/CMakeLists.txt b/test/internal_routing_disabled_acceptance_test/CMakeLists.txt index cce2eac69..4b6cdd20e 100644 --- a/test/internal_routing_disabled_acceptance_test/CMakeLists.txt +++ b/test/internal_routing_disabled_acceptance_test/CMakeLists.txt @@ -6,7 +6,7 @@ endif() project(internal_routing_disabled_acceptance_test LANGUAGES CXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -Wconversion -Wextra") -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD ${VSOMEIP_CXX_STANDARD}) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(THREADS_PREFER_PTHREAD_FLAG ON) diff --git a/test/network_tests/CMakeLists.txt b/test/network_tests/CMakeLists.txt index 198a20a3d..2a26dc98c 100644 --- a/test/network_tests/CMakeLists.txt +++ b/test/network_tests/CMakeLists.txt @@ -117,7 +117,7 @@ function(add_custom_test) # Skip valgrind if (DEFINED VALGRIND_TYPE AND NOT VALGRIND_TYPE STREQUAL "") set(TEST_ENTRYPOINT - ${TEST_ENTRYPOINT} --trace-children-skip=*/ssh${SUBPROCESSES},/usr/bin/seq + ${TEST_ENTRYPOINT} --trace-children-skip=*/ssh${SUBPROCESSES},/usr/bin/seq,/usr/bin/sleep,/usr/bin/netstat,/usr/bin/awk,/usr/bin/mawk,/usr/bin/grep,/usr/bin/wc,/usr/bin/pgrep ) endif() @@ -186,6 +186,7 @@ if(NOT ${TESTS_BAT}) add_subdirectory(subscribe_notify_one_tests) add_subdirectory(subscribe_notify_tests) add_subdirectory(suspend_resume_tests) + add_subdirectory(start_stop_start_tests) endif() add_subdirectory(application_tests) diff --git a/test/network_tests/big_payload_tests/conf/big_payload_test_local_starter.sh.in b/test/network_tests/big_payload_tests/conf/big_payload_test_local_starter.sh.in index 12275af27..ee99ea414 100755 --- a/test/network_tests/big_payload_tests/conf/big_payload_test_local_starter.sh.in +++ b/test/network_tests/big_payload_tests/conf/big_payload_test_local_starter.sh.in @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Copyright (C) 2015-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/test/network_tests/debounce_frequency_tests/conf/debounce_frequency_test_client.json.in b/test/network_tests/debounce_frequency_tests/conf/debounce_frequency_test_client.json.in index e0f2b0309..d604b2265 100644 --- a/test/network_tests/debounce_frequency_tests/conf/debounce_frequency_test_client.json.in +++ b/test/network_tests/debounce_frequency_tests/conf/debounce_frequency_test_client.json.in @@ -26,6 +26,7 @@ ] } ], + "routing": "routingmanagerd", "service-discovery": { "enable": "true", "multicast": "224.251.192.252", diff --git a/test/network_tests/debounce_frequency_tests/conf/debounce_frequency_test_service.json.in b/test/network_tests/debounce_frequency_tests/conf/debounce_frequency_test_service.json.in index eec804aed..597c6f5a8 100644 --- a/test/network_tests/debounce_frequency_tests/conf/debounce_frequency_test_service.json.in +++ b/test/network_tests/debounce_frequency_tests/conf/debounce_frequency_test_service.json.in @@ -16,6 +16,7 @@ "unreliable" : "30503" } ], + "routing": "routingmanagerd", "service-discovery" : { "enable" : "true", diff --git a/test/network_tests/fake_socket_tests/helpers/base_fake_socket_fixture.cpp b/test/network_tests/fake_socket_tests/helpers/base_fake_socket_fixture.cpp index a8e6af467..9a2e026c5 100644 --- a/test/network_tests/fake_socket_tests/helpers/base_fake_socket_fixture.cpp +++ b/test/network_tests/fake_socket_tests/helpers/base_fake_socket_fixture.cpp @@ -120,14 +120,16 @@ void base_fake_socket_fixture::clear_command_record(std::string const& _from, st socket_manager_->clear_command_record(_from, _to); } -/** - * @see socket_manager::wait_for_command - **/ [[nodiscard]] bool base_fake_socket_fixture::wait_for_command(std::string const& _from, std::string const& _to, protocol::id_e _id, std::chrono::milliseconds _timeout) { return socket_manager_->wait_for_command(_from, _to, _id, _timeout); } + void base_fake_socket_fixture::fail_on_bind(std::string const& _app, bool _fail) { socket_manager_->fail_on_bind(_app, _fail); } + +void base_fake_socket_fixture::set_ignore_broken_pipe(std::string const& _app_name, bool _set) { + socket_manager_->set_ignore_broken_pipe(_app_name, _set); +} } diff --git a/test/network_tests/fake_socket_tests/helpers/base_fake_socket_fixture.hpp b/test/network_tests/fake_socket_tests/helpers/base_fake_socket_fixture.hpp index 58a14d1c3..72f1d7311 100644 --- a/test/network_tests/fake_socket_tests/helpers/base_fake_socket_fixture.hpp +++ b/test/network_tests/fake_socket_tests/helpers/base_fake_socket_fixture.hpp @@ -100,7 +100,7 @@ struct base_fake_socket_fixture : ::testing::Test { * false, else. */ [[nodiscard]] bool await_connection(std::string const& _from, std::string const& _to, - std::chrono::milliseconds _timeout = std::chrono::seconds(1)); + std::chrono::milliseconds _timeout = std::chrono::seconds(3)); /** * Searches for a connection in which _from_name connected to an accepting _to_name. @@ -172,6 +172,11 @@ struct base_fake_socket_fixture : ::testing::Test { void fail_on_bind(std::string const& _app, bool _fail); + /** + * @see socket_manager::set_ignore_broken_pipe + */ + void set_ignore_broken_pipe(std::string const& _app_name, bool _set); + private: static std::shared_ptr factory_; std::shared_ptr socket_manager_{std::make_shared()}; diff --git a/test/network_tests/fake_socket_tests/helpers/fake_socket_factory.hpp b/test/network_tests/fake_socket_tests/helpers/fake_socket_factory.hpp index ad0df6196..b4b55d6a1 100644 --- a/test/network_tests/fake_socket_tests/helpers/fake_socket_factory.hpp +++ b/test/network_tests/fake_socket_tests/helpers/fake_socket_factory.hpp @@ -7,6 +7,7 @@ #define VSOMEIP_V3_TESTING_FAKE_SOCKET_FACTORY_HPP_ #include "../../../implementation/endpoints/include/abstract_socket_factory.hpp" +#include "../../../implementation/endpoints/include/asio_timer.hpp" #include "fake_netlink_connector.hpp" #include "fake_tcp_socket.hpp" @@ -49,6 +50,10 @@ class fake_socket_factory : public abstract_socket_factory { } return nullptr; } + virtual std::unique_ptr create_timer(boost::asio::io_context& _io) override { + // do not tinker with timeouts in network tests for now + return std::make_unique(_io); + } std::weak_ptr socket_manager_; }; diff --git a/test/network_tests/fake_socket_tests/helpers/fake_tcp_socket.hpp b/test/network_tests/fake_socket_tests/helpers/fake_tcp_socket.hpp index f453747fb..cae2b745b 100644 --- a/test/network_tests/fake_socket_tests/helpers/fake_tcp_socket.hpp +++ b/test/network_tests/fake_socket_tests/helpers/fake_tcp_socket.hpp @@ -120,6 +120,7 @@ class fake_tcp_socket : public tcp_socket { set_keep_alive_count_ = count; return true; } + [[nodiscard]] virtual bool set_quick_ack() { return true; } #endif #if defined(__linux__) || defined(__QNX__) diff --git a/test/network_tests/fake_socket_tests/helpers/fake_tcp_socket_handle.cpp b/test/network_tests/fake_socket_tests/helpers/fake_tcp_socket_handle.cpp index 002f121c0..ff578a182 100644 --- a/test/network_tests/fake_socket_tests/helpers/fake_tcp_socket_handle.cpp +++ b/test/network_tests/fake_socket_tests/helpers/fake_tcp_socket_handle.cpp @@ -245,6 +245,16 @@ void fake_tcp_socket_handle::write(std::vector const& boost::asio::post(io_, [size, handler = std::move(_handler)] { handler(boost::system::error_code(), size); }); return; } + + auto sm = [&]() -> std::shared_ptr { + auto const lock = std::scoped_lock(mtx_); + return socket_manager_.lock(); + }(); + + if (sm && sm->ignore_broken_pipe(*this)) { + return; + } + boost::asio::post(io_, [handler = std::move(_handler)] { if (!handler) { return; diff --git a/test/network_tests/fake_socket_tests/helpers/socket_manager.cpp b/test/network_tests/fake_socket_tests/helpers/socket_manager.cpp index b6faeef27..34a9692dd 100644 --- a/test/network_tests/fake_socket_tests/helpers/socket_manager.cpp +++ b/test/network_tests/fake_socket_tests/helpers/socket_manager.cpp @@ -429,6 +429,27 @@ void socket_manager::clear_command_record(std::string const& _from, std::string return false; } +void socket_manager::set_ignore_broken_pipe(std::string const& _app_name, bool _set) { + auto const lock = std::scoped_lock(mtx_); + if (_set) { + ignore_broken_pipe_.insert(_app_name); + } else { + ignore_broken_pipe_.erase(_app_name); + } +} + +bool socket_manager::ignore_broken_pipe(fake_tcp_socket_handle const& _handle) { + auto const lock = std::scoped_lock(mtx_); + auto app_name = _handle.get_app_name(); + auto const it = ignore_broken_pipe_.find(app_name); + if (it == ignore_broken_pipe_.end()) { + return false; + } + + LOCAL_LOG << "ignoring broken_pipe for app: " << app_name; + return true; +} + std::pair, std::weak_ptr> socket_manager::get_connection(std::string const& _from, std::string const& _to) { diff --git a/test/network_tests/fake_socket_tests/helpers/socket_manager.hpp b/test/network_tests/fake_socket_tests/helpers/socket_manager.hpp index 25b7a79e7..987aff80f 100644 --- a/test/network_tests/fake_socket_tests/helpers/socket_manager.hpp +++ b/test/network_tests/fake_socket_tests/helpers/socket_manager.hpp @@ -145,6 +145,17 @@ class socket_manager : public std::enable_shared_from_this { [[nodiscard]] bool wait_for_command(std::string const& _from, std::string const& _to, protocol::id_e _id, std::chrono::milliseconds _timeout = std::chrono::seconds(3)); + /** + * Set whether a write on a disconnected socket should result in a silent error, or a broken pipe + */ + void set_ignore_broken_pipe(std::string const& _app_name, bool _set); + + /** + * Called by a fake_tcp_socket_handle when a broken pipe situation occurs. + * @return true, if the broken pipe should be ignored. + **/ + [[nodiscard]] bool ignore_broken_pipe(fake_tcp_socket_handle const& _handle); + /** * associates a fake_tcp_socket_handle to a io_context and therefore to an app_name. **/ @@ -216,6 +227,7 @@ class socket_manager : public std::enable_shared_from_this { std::map> app_to_next_connection_errors_; std::set connections_to_ignore_; std::set fail_on_bind_; + std::set ignore_broken_pipe_; }; } diff --git a/test/network_tests/fake_socket_tests/test_connection_restoration.cpp b/test/network_tests/fake_socket_tests/test_connection_restoration.cpp index ac1116373..cc3bd7906 100644 --- a/test/network_tests/fake_socket_tests/test_connection_restoration.cpp +++ b/test/network_tests/fake_socket_tests/test_connection_restoration.cpp @@ -43,14 +43,18 @@ struct test_client_helper : public base_fake_socket_fixture { server_->offer_field(offered_field_); } - void start_apps() { - start_router(); - start_server(); - + void start_client_app() { client_ = start_client(client_name_); ASSERT_NE(client_, nullptr); ASSERT_TRUE(client_->app_state_record_.wait_for(vsomeip::state_type_e::ST_REGISTERED)); } + + void start_apps() { + start_router(); + start_server(); + start_client_app(); + } + [[nodiscard]] bool subscribe_to_event(std::chrono::milliseconds timeout = std::chrono::seconds(6)) { client_->request_service(service_instance_); client_->subscribe_event(offered_event_); @@ -134,6 +138,29 @@ TEST_F(test_client_helper, request_reply_with_sub) { EXPECT_TRUE(client_->message_record_.wait_for(expected_reply_)); } +TEST_F(test_client_helper, request_reply_routingd) { + /// another boring request-reply, but this time the server is routingd + + start_router(); + start_client_app(); + // no server! router will be server + + // routingd offers service + routingmanagerd_->offer(service_instance_); + // ..and answers + std::vector payload{0x2, 0x3}; + routingmanagerd_->answer_request(request_, [payload] { return payload; }); + expected_reply_.payload_ = payload; + + client_->request_service(service_instance_); + + EXPECT_TRUE(client_->availability_record_.wait_for(service_availability::available(service_instance_))); + client_->send_request(request_); + + ASSERT_TRUE(routingmanagerd_->message_record_.wait_for(expected_request_)); + EXPECT_TRUE(client_->message_record_.wait_for(expected_reply_)); +} + TEST_F(test_client_helper, the_server_sends_subscribe_ack_when_the_routing_info_is_late) { start_apps(); send_field_message(); @@ -333,6 +360,38 @@ TEST_F(test_client_helper, client_ignores_server_connection_attempt_once) { EXPECT_TRUE(subscribe_to_event()); } +TEST_F(test_client_helper, client_ignores_server_connections_for_a_while) { + /// a somewhat more complex version of `client_ignores_server_connection_attempt_once` + /// except not only are the connections ignored, but also any writes + + start_apps(); + + // client ignores connections + set_ignore_connections(client_name_, true); + // server has to ignore any broken pipes - so that not only does `connect()` never return, but also no `async_write` callbacks + set_ignore_broken_pipe(server_name_, true); + // server has to answer with an initial value + send_field_message(); + + // client subscribes to field + client_->request_service(service_instance_); + client_->subscribe_field(offered_field_); + + // unfortunate, but we need to wait for magics amount of time - there will be background attempts to connect+write failures + EXPECT_FALSE(client_->subscription_record_.wait_for(event_subscription::successfully_subscribed_to(offered_field_), + std::chrono::seconds(1))); + + // accept connections again + // (and no need to worry about the broken pipe) + set_ignore_connections(client_name_, false); + + // CONFIG_ID should be sent when the connection is established + EXPECT_TRUE(await_connection(server_name_, client_name_)); + EXPECT_TRUE(wait_for_command(server_name_, client_name_, protocol::id_e::CONFIG_ID)); + EXPECT_TRUE(client_->subscription_record_.wait_for(event_subscription::successfully_subscribed_to(offered_field_))); + EXPECT_TRUE(client_->message_record_.wait_for(first_expected_field_message_)); +} + TEST_F(test_client_helper, given_server_to_client_breaksdown_when_the_connection_is_re_established_then_the_subscription_confirmed) { start_apps(); send_field_message(); diff --git a/test/network_tests/header_factory_tests/conf/header_factory_test_send_receive_starter.sh.in b/test/network_tests/header_factory_tests/conf/header_factory_test_send_receive_starter.sh.in index c9ce0ea06..cc0ef4cb3 100755 --- a/test/network_tests/header_factory_tests/conf/header_factory_test_send_receive_starter.sh.in +++ b/test/network_tests/header_factory_tests/conf/header_factory_test_send_receive_starter.sh.in @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Copyright (C) 2015-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/test/network_tests/initial_event_tests/conf/initial_event_test_master_starter_qnx.sh.in b/test/network_tests/initial_event_tests/conf/initial_event_test_master_starter_qnx.sh.in index f2d8d5470..59211e617 100755 --- a/test/network_tests/initial_event_tests/conf/initial_event_test_master_starter_qnx.sh.in +++ b/test/network_tests/initial_event_tests/conf/initial_event_test_master_starter_qnx.sh.in @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Copyright (C) 2015-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/test/network_tests/offer_tests/CMakeLists.txt b/test/network_tests/offer_tests/CMakeLists.txt index cdd95ebce..2668a23f9 100644 --- a/test/network_tests/offer_tests/CMakeLists.txt +++ b/test/network_tests/offer_tests/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (C) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (C) 2023-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -18,7 +18,6 @@ set(configuration_files offer_test_local.json offer_test_local_starter.sh offer_test_multiple_offerings.json - offer_test_multiple_offerings_starter.sh ) configure_files("${configuration_files}") @@ -81,19 +80,19 @@ add_custom_test( add_custom_test( NAME offer_test_external COMMAND ${CMAKE_CURRENT_BINARY_DIR}/offer_test_external_master_starter.sh - TIMEOUT 360 + TIMEOUT 60 ) # Add custom test command. add_custom_test( NAME offer_test_big_sd_msg COMMAND ${CMAKE_CURRENT_BINARY_DIR}/offer_test_big_sd_msg_master_starter.sh - TIMEOUT 360 + TIMEOUT 60 ) # Add custom test command. add_custom_test( NAME offer_test_multiple_offerings - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/offer_test_multiple_offerings_starter.sh - TIMEOUT 1800 + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/offer_test_multiple_offerings + TIMEOUT 60 ) diff --git a/test/network_tests/offer_tests/conf/offer_test_big_sd_msg_master_starter.sh.in b/test/network_tests/offer_tests/conf/offer_test_big_sd_msg_master_starter.sh.in index e934238b3..1d8fc22c4 100755 --- a/test/network_tests/offer_tests/conf/offer_test_big_sd_msg_master_starter.sh.in +++ b/test/network_tests/offer_tests/conf/offer_test_big_sd_msg_master_starter.sh.in @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (C) 2015-2017 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (C) 2015-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -14,25 +14,22 @@ FAIL=0 export VSOMEIP_CONFIGURATION=offer_test_big_sd_msg_master.json # start daemon +export VSOMEIP_APPLICATION_NAME="client-routingmanager" ../../../examples/routingmanagerd/routingmanagerd & PID_VSOMEIPD=$! +export VSOMEIP_APPLICATION_NAME="client-sample" ./offer_test_big_sd_msg_client & CLIENT_PID=$! - -sleep 1 - +SERVICE_PID=0 if [ ! -z "$USE_LXC_TEST" ]; then - echo "Waiting for 5s" - sleep 5 echo "starting offer test on slave LXC offer_test_big_sd_msg_slave_starter.sh" ssh -tt -i $SANDBOX_ROOT_DIR/commonapi_main/lxc-config/.ssh/mgc_lxc/rsa_key_file.pub -o StrictHostKeyChecking=no root@$LXC_TEST_SLAVE_IP "bash -ci \"set -m; cd \\\$SANDBOX_TARGET_DIR/vsomeip_lib/test/network_tests/offer_tests; ./offer_test_big_sd_msg_slave_starter.sh\"" & - echo "remote ssh pid: $!" + SERVICE_PID=$! elif [ ! -z "$USE_DOCKER" ]; then - echo "Waiting for 5s" - sleep 5 - docker exec $DOCKER_IMAGE sh -c "cd $DOCKER_TESTS && sleep 10; ./offer_test_big_sd_msg_slave_starter.sh" & + docker exec $DOCKER_IMAGE sh -c "cd $DOCKER_TESTS; ./offer_test_big_sd_msg_slave_starter.sh" & + SERVICE_PID=$! else cat < its_lock(mutex_); @@ -151,7 +151,7 @@ class offer_test_big_sd_msg_client : public vsomeip_utilities::base_logger { condition_.wait(its_lock, [this] { return !wait_until_service_available_; }); condition_.wait(its_lock, [this] { return !wait_until_subscribed_; }); - std::this_thread::sleep_for(std::chrono::seconds(5)); + std::this_thread::sleep_for(std::chrono::seconds(3)); std::shared_ptr its_req = vsomeip::runtime::get()->create_request(); its_req->set_service(1); its_req->set_instance(1); diff --git a/test/network_tests/offer_tests/offer_test_big_sd_msg_service.cpp b/test/network_tests/offer_tests/offer_test_big_sd_msg_service.cpp index 52d282e20..97e27caea 100644 --- a/test/network_tests/offer_tests/offer_test_big_sd_msg_service.cpp +++ b/test/network_tests/offer_tests/offer_test_big_sd_msg_service.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// Copyright (C) 2014-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -81,8 +81,7 @@ class offer_test_big_sd_msg_service : public vsomeip_utilities::base_logger { (void)_uid; (void)_gid; if (_subscribed) { - subscriptions_[_service]++; - EXPECT_EQ(1u, subscriptions_[_service]); + subscriptions_[_service] = 1; if (std::all_of(subscriptions_.begin(), subscriptions_.end(), [&](const subscriptions_t::value_type& v) { return v.second == 1; })) { std::lock_guard its_lock(mutex_); @@ -130,7 +129,7 @@ class offer_test_big_sd_msg_service : public vsomeip_utilities::base_logger { bool wait_until_client_subscribed_to_all_services_; std::mutex mutex_; std::condition_variable condition_; - std::atomic shutdown_method_called_; + std::atomic_bool shutdown_method_called_; typedef std::map subscriptions_t; subscriptions_t subscriptions_; std::thread offer_thread_; diff --git a/test/network_tests/offer_tests/offer_test_client.cpp b/test/network_tests/offer_tests/offer_test_client.cpp index b3410cb69..1472a6f41 100644 --- a/test/network_tests/offer_tests/offer_test_client.cpp +++ b/test/network_tests/offer_tests/offer_test_client.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// Copyright (C) 2014-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -176,11 +176,8 @@ class offer_test_client : public vsomeip_utilities::base_logger { its_req->set_method(service_info_.method_id); app_->send(its_req); std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } else { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); { std::lock_guard its_lock(stop_mutex_); wait_for_stop_ = false; diff --git a/test/network_tests/offer_tests/offer_test_external_sd_msg_sender.cpp b/test/network_tests/offer_tests/offer_test_external_sd_msg_sender.cpp index d68e6a482..85218a2ba 100644 --- a/test/network_tests/offer_tests/offer_test_external_sd_msg_sender.cpp +++ b/test/network_tests/offer_tests/offer_test_external_sd_msg_sender.cpp @@ -9,6 +9,7 @@ #include #include +#include static char* passed_address; @@ -27,6 +28,7 @@ TEST(someip_offer_test, send_offer_service_sd_message) { for (int var = 0; var < 15; ++var) { udp_socket.send_to(boost::asio::buffer(its_offer_service_message), target_sd); ++its_offer_service_message[11]; + std::this_thread::sleep_for(std::chrono::seconds(1)); } // call shutdown method diff --git a/test/network_tests/offer_tests/offer_test_multiple_offerings.cpp b/test/network_tests/offer_tests/offer_test_multiple_offerings.cpp index 799d9f528..f4f95c85f 100644 --- a/test/network_tests/offer_tests/offer_test_multiple_offerings.cpp +++ b/test/network_tests/offer_tests/offer_test_multiple_offerings.cpp @@ -240,6 +240,7 @@ class vsomeip_daemon { }; TEST(offer_test, multiple_offerings_same_service) { + setenv("VSOMEIP_CONFIGURATION", "offer_test_multiple_offerings.json", 1); const service_t service_id{0xfee2}; const instance_t instance_id{0x0001}; const major_version_t major{1}; @@ -259,17 +260,14 @@ TEST(offer_test, multiple_offerings_same_service) { client.wait_availability(); VSOMEIP_INFO << "[TEST] Client is AVAILABLE"; - // Without suspending the deamon, the client immediatly closes. + // Without suspending the deamon, the client immediately closes. daemon.set_routing_state(routing_state_e::RS_SUSPENDED); for (int i = 0; i < REQUESTS_NUMBER; ++i) { VSOMEIP_INFO << "[TEST] Sending Loop " << i; - // NOTE: Don't remove the sleep. VSOME/IP needs some time until it sends a PONG message to - // services. Otherwise we can't detect service availability correctly later. - std::this_thread::sleep_for(TIMEOUT_RESPONSE); std::vector out_payload = {uint8_t(i)}; client.send_message(out_payload); - // Independant of the number of offerings, the client must never fail it's assertions. + // Independent of the number of offerings, the client must never fail it's assertions. ASSERT_TRUE(client.was_message_received()) << "Message was not received"; // Should be safe to read the payload without locks. No one is reading or writing to it. EXPECT_EQ(out_payload, client.getReceivedPayload()) << "Payload was not equal"; diff --git a/test/network_tests/offer_tests/offer_test_service_external.cpp b/test/network_tests/offer_tests/offer_test_service_external.cpp index 87fc8f36e..e2cc54658 100644 --- a/test/network_tests/offer_tests/offer_test_service_external.cpp +++ b/test/network_tests/offer_tests/offer_test_service_external.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2014-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// Copyright (C) 2014-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -61,7 +61,7 @@ class offer_test_service : public vsomeip_utilities::base_logger { << (_state == vsomeip::state_type_e::ST_REGISTERED ? "registered." : "deregistered."); if (_state == vsomeip::state_type_e::ST_REGISTERED) { - std::lock_guard its_lock(mutex_); + std::scoped_lock its_lock(mutex_); wait_until_registered_ = false; condition_.notify_one(); } @@ -70,7 +70,7 @@ class offer_test_service : public vsomeip_utilities::base_logger { void on_availability(vsomeip::service_t _service, vsomeip::instance_t _instance, bool _is_available) { VSOMEIP_INFO << "Service [" << std::hex << std::setfill('0') << std::setw(4) << _service << "." << _instance << "] is " << (_is_available ? "available" : "not available") << "."; - std::lock_guard its_lock(mutex_); + std::scoped_lock its_lock(mutex_); if (_is_available) { wait_until_service_available_ = false; condition_.notify_one(); @@ -92,7 +92,6 @@ class offer_test_service : public vsomeip_utilities::base_logger { condition_.wait(its_lock, [this] { return !wait_until_service_available_; }); } - std::this_thread::sleep_for(std::chrono::seconds(1)); VSOMEIP_DEBUG << "[" << std::hex << std::setfill('0') << std::setw(4) << service_info_.service_id << "] Calling stop method"; std::shared_ptr msg(vsomeip::runtime::get()->create_request()); msg->set_service(service_info_.service_id); diff --git a/test/network_tests/payload_tests/conf/external_local_payload_test_client_external_starter.sh.in b/test/network_tests/payload_tests/conf/external_local_payload_test_client_external_starter.sh.in index 6454b889b..986353773 100755 --- a/test/network_tests/payload_tests/conf/external_local_payload_test_client_external_starter.sh.in +++ b/test/network_tests/payload_tests/conf/external_local_payload_test_client_external_starter.sh.in @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (C) 2015-2017 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (C) 2015-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. @@ -10,6 +10,7 @@ # the testcase simply executes this script. This script then runs client # and service and checks that both exit sucessfully. +# Initialize error tracking FAIL=0 # Parameter 1: the pid to check @@ -52,19 +53,22 @@ check_tcp_udp_sockets_are_closed () export VSOMEIP_APPLICATION_NAME=external_local_payload_test_service export VSOMEIP_CONFIGURATION=external_local_payload_test_service.json ./payload_test_service --udp & -SERIVCE_PID=$! +SERVICE_PID=$! -# Ensure that service application starts before client +# To ensure that sockets are open sleep 2 +# Initialize EXTERNAL_CLIENT_PID to avoid unbound variable error +EXTERNAL_CLIENT_PID=0 # Display a message to show the user that he must now call the external client # to finish the test successfully if [ ! -z "$USE_LXC_TEST" ]; then echo "starting external local payload on slave LXC" ssh -tt -i $SANDBOX_ROOT_DIR/commonapi_main/lxc-config/.ssh/mgc_lxc/rsa_key_file.pub -o StrictHostKeyChecking=no root@$LXC_TEST_SLAVE_IP "bash -ci \"set -m; cd \\\$SANDBOX_TARGET_DIR/vsomeip_lib/test/network_tests/payload_tests; ./external_local_payload_test_client_external_start.sh\"" & - echo "remote ssh job id: $!" + EXTERNAL_CLIENT_PID=$! elif [ ! -z "$USE_DOCKER" ]; then docker exec $DOCKER_IMAGE sh -c "cd $DOCKER_TESTS && ./external_local_payload_test_client_external_start.sh" & + EXTERNAL_CLIENT_PID=$! else cat <`: Custom size in bytes (default: UDS) + +*Performance Tuning:* +- `--sliding-window-size`: Number of messages to send before waiting for acknowledgment (async mode) +- `--number-of-messages`: Number of messages to send per payload size iteration + +*Service Control:* +- `--dont-shutdown-service`: Don't shutdown the service when test completes +- `--help`: Print help information + + +## Test Cases + +### 1. local_payload_test - Basic Local Communication + +Tests basic request-response communication between local service and client. + +**Test Flow:** +1. Start local service offering the test service +2. Start local client that discovers and connects to the service +3. Client sends messages with increasing payload sizes +4. Service validates each message and sends acknowledgment responses + +**Success Criteria:** +- All messages sent/received successfully +- No hanging TCP/UDP sockets after completion +- Both processes exit with code 0 + +### 2. local_payload_test_huge_payload - Large Payload Stress Test + +Tests the system's ability to handle very large payloads. + +**Test Flow:** +1. Start local service offering the test service +2. Start local client that discovers and connects to the service +3. Client sends several messages with a specific payload size +4. Service processes and validates each large payload + +**Success Criteria:** +- Successfully sends/receives messages with a specific set payload +- Service processes all large payloads correctly + +### 3. external_local_payload_test_client_external - Cross-Container Communication + +Tests communication between local service and external client running in Docker container. + +**Test Flow:** +1. **UDP Phase:** Start local service with `--udp`, external client tests UDP communication +2. External client waits 5 seconds for service transition +3. **TCP Phase:** Restart local service with `--tcp`, external client tests TCP communication +4. Validate both transport protocols work across container boundaries + +**Success Criteria:** +- Both UDP and TCP phases complete successfully +- Cross-container communication works properly +- Service can switch between transport protocols + +### 4. external_local_payload_test_client_local_and_external - Mixed Client Test + +Tests simultaneous communication with both local and external clients. + +**Test Flow:** +1. Start local service offering the test service +2. Start local client with `--dont-shutdown-service` flag (runs in background) +3. **UDP Phase:** External client tests UDP communication while local client is active +4. Service handles both local and external clients simultaneously +5. External client waits 5 seconds for service transition +6. **TCP Phase:** Restart service with `--tcp`, external client tests TCP communication +7. Validate multi-client scenarios + +**Success Criteria:** +- Service handles multiple concurrent clients (local + external) +- Both UDP and TCP phases work with mixed client setup +- No interference between local and external clients diff --git a/test/network_tests/regression_tests/climate_test_client.cpp b/test/network_tests/regression_tests/climate_test_client.cpp index 5fabca383..ccf882c48 100644 --- a/test/network_tests/regression_tests/climate_test_client.cpp +++ b/test/network_tests/regression_tests/climate_test_client.cpp @@ -84,6 +84,7 @@ class client_sample { for (uint32_t i = 0; i < its_payload->get_length(); ++i) { its_message << std::setw(2) << static_cast(its_payload->get_data()[i]) << " "; } + VSOMEIP_INFO << its_message.str(); if ((its_payload->get_length() % 5) == 0) { notifications_received++; @@ -108,7 +109,7 @@ class client_sample { } else if (notifications_received == 4) { // All expected notifications received, stop the client and send shutdown message to // service - EXPECT_EQ(availability_handler_calls, 9); + EXPECT_EQ(availability_handler_calls, 7); std::shared_ptr its_set = vsomeip::runtime::get()->create_message(); its_set->set_message_type(vsomeip_v3::message_type_e::MT_REQUEST_NO_RETURN); diff --git a/test/network_tests/regression_tests/conf/climate_test_slave.json.in b/test/network_tests/regression_tests/conf/climate_test_slave.json.in index 51e0767a0..c3b748407 100644 --- a/test/network_tests/regression_tests/conf/climate_test_slave.json.in +++ b/test/network_tests/regression_tests/conf/climate_test_slave.json.in @@ -66,6 +66,7 @@ ] } ], + "routing" : "routingmanagerd", "service-discovery" : { "enable" : "true", diff --git a/test/network_tests/regression_tests/conf/climate_test_slave_starter.sh.in b/test/network_tests/regression_tests/conf/climate_test_slave_starter.sh.in index 0b6d0feab..01bfeecc5 100755 --- a/test/network_tests/regression_tests/conf/climate_test_slave_starter.sh.in +++ b/test/network_tests/regression_tests/conf/climate_test_slave_starter.sh.in @@ -11,19 +11,22 @@ # and checks that all exit successfully. FAIL=0 +export VSOMEIP_CONFIGURATION=climate_test_slave.json +export VSOMEIP_APPLICATION_NAME=routingmanagerd +# start daemon +../../../examples/routingmanagerd/routingmanagerd & +PID_VSOMEIPD=$! # Start the services -export VSOMEIP_CONFIGURATION=climate_test_slave.json export VSOMEIP_APPLICATION_NAME=client-sample ./climate_test_client & +TEST_CLIENT_PID=$! # Wait until all applications are finished -for job in $(jobs -p) -do - # Fail gets incremented if one of the binaries exits - # with a non-zero exit code - wait $job || ((FAIL+=1)) -done +wait $TEST_CLIENT_PID || FAIL=$(($FAIL+1)) + +kill $PID_VSOMEIPD +wait $PID_VSOMEIPD # Check if both exited successfully if [ $FAIL -eq 0 ] diff --git a/test/network_tests/routing_tests/conf/external_local_routing_test_starter.sh.in b/test/network_tests/routing_tests/conf/external_local_routing_test_starter.sh.in index 839e9e9b9..49a91ce6b 100755 --- a/test/network_tests/routing_tests/conf/external_local_routing_test_starter.sh.in +++ b/test/network_tests/routing_tests/conf/external_local_routing_test_starter.sh.in @@ -52,7 +52,8 @@ export VSOMEIP_APPLICATION_NAME=external_local_routing_test_service export VSOMEIP_CONFIGURATION=external_local_routing_test_service.json ./external_local_routing_test_service & SERIVCE_PID=$! -sleep 4; +# Wait to let the socket be opened +sleep 2; check_tcp_udp_sockets_are_open $SERIVCE_PID diff --git a/test/network_tests/routing_tests/conf/local_routing_test_starter.sh.in b/test/network_tests/routing_tests/conf/local_routing_test_starter.sh.in index f6c8ea97b..b8b0b89a2 100755 --- a/test/network_tests/routing_tests/conf/local_routing_test_starter.sh.in +++ b/test/network_tests/routing_tests/conf/local_routing_test_starter.sh.in @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Copyright (C) 2015-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this @@ -42,8 +42,6 @@ PID_VSOMEIPD=$! export VSOMEIP_APPLICATION_NAME=local_routing_test_service ./local_routing_test_service & SERVICE_PID=$! -WAIT_PID_ONE=$! -sleep 4; check_tcp_udp_sockets_are_closed $SERVICE_PID @@ -52,7 +50,6 @@ export VSOMEIP_APPLICATION_NAME=local_routing_test_client export VSOMEIP_CONFIGURATION=local_routing_test_client.json ./local_routing_test_client & CLIENT_PID=$! -WAIT_PID_TWO=$! check_tcp_udp_sockets_are_closed $SERVICE_PID check_tcp_udp_sockets_are_closed $CLIENT_PID @@ -60,8 +57,8 @@ check_tcp_udp_sockets_are_closed $CLIENT_PID # Wait until client and service are finished # Fail gets incremented if either client or service exit # with a non-zero exit code -wait $WAIT_PID_ONE || ((FAIL+=1)) -wait $WAIT_PID_TWO || ((FAIL+=1)) +wait $SERVICE_PID || ((FAIL+=1)) +wait $CLIENT_PID || ((FAIL+=1)) kill $PID_VSOMEIPD diff --git a/test/network_tests/routing_tests/docs/external_local_routing_test_client.png b/test/network_tests/routing_tests/docs/external_local_routing_test_client.png new file mode 100644 index 000000000..c30256c26 Binary files /dev/null and b/test/network_tests/routing_tests/docs/external_local_routing_test_client.png differ diff --git a/test/network_tests/routing_tests/docs/external_local_routing_test_client.puml b/test/network_tests/routing_tests/docs/external_local_routing_test_client.puml new file mode 100644 index 000000000..bf7b52ae4 --- /dev/null +++ b/test/network_tests/routing_tests/docs/external_local_routing_test_client.puml @@ -0,0 +1,45 @@ +@startuml + +Actor service_consumer as SC + +participant routing_manager as RM + +SC -> RM : registration process + +RM --\ SC : REGISTER_ACK + +SC -> RM : REQUEST SERVICE + +note right of SC + Service Consumer waits for + service availability +end note + +RM --\ SC : ON_AVAILABLE + +note right of SC + Service is now available, + consumer can send requests +end note + +loop For each request to send + + SC -> RM : REQUEST + + RM -> SC : RESPONSE + + note right of SC + Service Consumer validates + the received response + end note + +end + +note right of SC + All responses received, + test completed successfully +end note + +SC -> RM : STOP_METHOD + +@enduml diff --git a/test/network_tests/routing_tests/docs/external_local_routing_test_service.png b/test/network_tests/routing_tests/docs/external_local_routing_test_service.png new file mode 100644 index 000000000..3401acd81 Binary files /dev/null and b/test/network_tests/routing_tests/docs/external_local_routing_test_service.png differ diff --git a/test/network_tests/routing_tests/docs/external_local_routing_test_service.puml b/test/network_tests/routing_tests/docs/external_local_routing_test_service.puml new file mode 100644 index 000000000..eab5864a4 --- /dev/null +++ b/test/network_tests/routing_tests/docs/external_local_routing_test_service.puml @@ -0,0 +1,40 @@ +@startuml + +Actor service_provider as SP + +participant routing_manager as RM + +SP -> RM : registration process + +RM --\ SP : REGISTER_ACK + +SP -> RM : OFFER + +note right of SP + Service Provider is now ready + to receive requests +end note + +loop Until STOP_METHOD received + + RM -> SP : REQUEST + + note right of SP + Service Provider processes + the received request + end note + + SP -> RM : RESPONSE + +end + +RM -> SP : STOP_METHOD + +SP -> RM : STOP OFFER + +note right of SP + Service Provider cleans up + and terminates +end note + +@enduml diff --git a/test/network_tests/routing_tests/docs/local_routing_test_client.png b/test/network_tests/routing_tests/docs/local_routing_test_client.png new file mode 100644 index 000000000..2801e538b Binary files /dev/null and b/test/network_tests/routing_tests/docs/local_routing_test_client.png differ diff --git a/test/network_tests/routing_tests/docs/local_routing_test_client.puml b/test/network_tests/routing_tests/docs/local_routing_test_client.puml new file mode 100644 index 000000000..8cc6b6096 --- /dev/null +++ b/test/network_tests/routing_tests/docs/local_routing_test_client.puml @@ -0,0 +1,47 @@ +@startuml + +Actor service_consumer as SC + +participant routing_manager as RM + +participant service_provider as SP + +SC -> RM : registration process + +RM --\ SC : REGISTER_ACK + +SC -> RM : REQUEST SERVICE + +note right of SC + Service Consumer waits for + service availability +end note + +RM --\ SC : ON_AVAILABLE + +note right of SC + Service is now available, + consumer can send requests +end note + +loop For each request to send + + SC -> SP : REQUEST + + SP -> SC : RESPONSE + + note right of SC + Service Consumer validates + the received response + end note + +end + +note right of SC + All responses received, + test completed successfully +end note + +SC -> SP : STOP_METHOD + +@enduml diff --git a/test/network_tests/routing_tests/docs/local_routing_test_service.png b/test/network_tests/routing_tests/docs/local_routing_test_service.png new file mode 100644 index 000000000..d24d2c3c7 Binary files /dev/null and b/test/network_tests/routing_tests/docs/local_routing_test_service.png differ diff --git a/test/network_tests/routing_tests/docs/local_routing_test_service.puml b/test/network_tests/routing_tests/docs/local_routing_test_service.puml new file mode 100644 index 000000000..5c0d3368b --- /dev/null +++ b/test/network_tests/routing_tests/docs/local_routing_test_service.puml @@ -0,0 +1,42 @@ +@startuml + +Actor service_provider as SP + +participant routing_manager as RM + +participant service_consumer as SC + +SP -> RM : registration process + +RM --\ SP : REGISTER_ACK + +SP -> RM : OFFER + +note right of SP + Service Provider is now ready + to receive requests +end note + +loop Until STOP_METHOD received + + SC -> SP : REQUEST + + note right of SP + Service Provider processes + the received request + end note + + SP -> SC : RESPONSE + +end + +SC -> SP : STOP_METHOD + +SP -> RM : STOP OFFER + +note right of SP + Service Provider cleans up + and terminates +end note + +@enduml diff --git a/test/network_tests/routing_tests/routing_test.md b/test/network_tests/routing_tests/routing_test.md new file mode 100644 index 000000000..b721d6f60 --- /dev/null +++ b/test/network_tests/routing_tests/routing_test.md @@ -0,0 +1,55 @@ +# Routing Tests + +This test validates the routing manager functionality in vsomeip, ensuring proper service discovery, message routing between applications, and handling of routing-related operations in different configurations. + +## Purpose + +- Verify that the routing manager correctly manages service registrations and availability notifications +- Ensure proper message routing between service providers and consumers through the routing manager +- Validate routing manager's handling of multiple clients and services +- Test routing manager failover and state management +- Ensure correct behavior in both local and external routing scenarios + +## Test Logic + +### Service provider + +Service-provider, after registering with the routing manager and offering the service, will wait for incoming requests. For each received request, it will process the message and send back a response through the routing manager. After receiving all expected messages, it will wait for a STOP_METHOD request to terminate. + +### Local Routing + +![Diagram](docs/local_routing_test_service.png) + +### External Routing + +![Diagram](docs/external_local_routing_test_service.png) + +### Service consumer + +After requesting the service and receiving its availability notification from the routing manager, the client will send a set number of requests to the service. It will then validate each received response to ensure correct routing and message delivery. When all expected responses are received, it will send a STOP_METHOD request to stop the service-provider. + +### Local Routing + +![Diagram](docs/local_routing_test_client.png) + +### External Routing + +![Diagram](docs/external_local_routing_test_client.png) + +## Test Scenarios + +### Local Routing + +Tests routing functionality when all applications run on the same device, using local (Unix domain socket) communication. + +### External Routing + +Tests routing functionality when applications communicate over network interfaces, validating TCP/UDP routing capabilities. + +## Expected Results + +- All applications successfully register with the routing manager +- Service availability notifications are correctly delivered to consumers +- Messages are properly routed between providers and consumers +- No messages are lost or misrouted +- Proper cleanup and deregistration occurs when applications terminate diff --git a/test/network_tests/second_address_tests/conf/second_address_test_master_starter.sh.in b/test/network_tests/second_address_tests/conf/second_address_test_master_starter.sh.in index 66f7d5600..20ddb3f21 100755 --- a/test/network_tests/second_address_tests/conf/second_address_test_master_starter.sh.in +++ b/test/network_tests/second_address_tests/conf/second_address_test_master_starter.sh.in @@ -34,21 +34,23 @@ elif [ "$OPERATIONMODE" = "CLIENT" ]; then export VSOMEIP_CONFIGURATION=second_address_test_master_client.json fi -rm -f /tmp/vsomeip* - +export VSOMEIP_APPLICATION_NAME=routingmanagerd ../../../examples/routingmanagerd/routingmanagerd & PID_VSOMEIPD=$! +export VSOMEIP_APPLICATION_NAME=$MASTER_APPLICATION ./$MASTER_APPLICATION $COMMUNICATIONMODE & PID_MASTER=$! -sleep 1 +PID_SLAVE=0 if [ ! -z "$USE_LXC_TEST" ]; then echo "starting offer test on slave LXC second_address_test_slave_starter.sh" ssh -tt -i $SANDBOX_ROOT_DIR/commonapi_main/lxc-config/.ssh/mgc_lxc/rsa_key_file.pub -o StrictHostKeyChecking=no root@$LXC_TEST_SLAVE_IP "bash -ci \"set -m; cd \\\$SANDBOX_TARGET_DIR/vsomeip_lib/test/network_tests/second_address_tests; ./second_address_test_slave_starter.sh $SLAVE_OPERATIONMODE $COMMUNICATIONMODE\"" & + PID_SLAVE=$! elif [ ! -z "$USE_DOCKER" ]; then - docker exec $DOCKER_IMAGE sh -c "cd $DOCKER_TESTS; sleep 10; ./second_address_test_slave_starter.sh $SLAVE_OPERATIONMODE $COMMUNICATIONMODE" & + docker exec $DOCKER_IMAGE sh -c "cd $DOCKER_TESTS; ./second_address_test_slave_starter.sh $SLAVE_OPERATIONMODE $COMMUNICATIONMODE" & + PID_SLAVE=$! else cat < SC : OFFER + +SC -> SP : REQUEST + +loop For messages to send + SC -> SC : set payload + + SC -> SC : set data + + SC -> SP : send message + + SC -> SC : messages_sent_++ + + note across + Wait for response + endnote + + SP --> SC : response +end + +SC -> SP : REQUEST event + +SC -> SP : REQUEST selective event + +SC -> SP : SUBSCRIBE eventgroup + +SC -> SP : SUBSCRIBE selective eventgroup + +note across + Wait for subscribe acks +endnote + +SP --> SC : SUBSCRIBE ACK eventgroup + +SP --> SC : SUBSCRIBE ACK selective eventgroup + +SC -> SP : SEND notify method + +note across + Wait for all messages to be received +endnote + +loop For messages received in background + alt If selective event received + SC -> SC : wait_until_selective_events_received_++ + else + SC -> SC : wait_until_events_received_++ + end +end + +SC -> SP : SEND shutdown method + +note across + Wait for response to shutdown method +endnote + +SP --> SC : response + +SC -> SC : stop + +@enduml diff --git a/test/network_tests/second_address_tests/docs/second_address_test_service.png b/test/network_tests/second_address_tests/docs/second_address_test_service.png new file mode 100644 index 000000000..0ebb33bdd Binary files /dev/null and b/test/network_tests/second_address_tests/docs/second_address_test_service.png differ diff --git a/test/network_tests/second_address_tests/docs/second_address_test_service.puml b/test/network_tests/second_address_tests/docs/second_address_test_service.puml new file mode 100644 index 000000000..049bab65f --- /dev/null +++ b/test/network_tests/second_address_tests/docs/second_address_test_service.puml @@ -0,0 +1,51 @@ +@startuml + +Actor Service_Provider as SP + +participant Routing_Manager as RM + +participant Service_Consumer as SC + +SP -> RM : OFFER + +note across + Wait for messages for request_method are received from Service_Consumer +endnote + +SC --> SP : SEND request method + +loop For messages received in background + SP -> SP : set payload + + SP -> SC : send response + + SP -> SP : messages_received_++ +end + +note across + Wait for message for notify method to be received from Service_Consumer +endnote + +SC --> SP : SEND notify method + +loop For i < notifications_to_send_ + SP -> SP : set payload + + SP -> SC : send event + + SP -> SC : send selective event +end + +note across + Wait for message for shutdown method to be received from Service_Consumer +endnote + +SC --> SP : SEND shutdown method + +SP -> SC : send response + +SP -> RM : STOP OFFER + +SP -> SP : stop + +@enduml diff --git a/test/network_tests/second_address_tests/second_address_test.md b/test/network_tests/second_address_tests/second_address_test.md new file mode 100644 index 000000000..c4ac7d5b0 --- /dev/null +++ b/test/network_tests/second_address_tests/second_address_test.md @@ -0,0 +1,25 @@ +# Second Address Tests + +This test validates that vsomeip applications can communicate correctly regardless of the IP address. The test verifies that services can be discovered and accessed across different IP addresses, ensuring proper routing and endpoint management. + +## Purpose + +Depending on network configuration: + +- Assure that services can be offered and discovered across multiple IP addresses +- Validate proper routing manager behavior with multiple network addresses +- Test event subscription and notification across different IP addresses + +## Test Logic + +### Service provider + +Service provider, after registering and becoming available, starts offering the service on its configured IP address. It handles incoming requests, processes subscription requests for both regular and selective events, and responds to notification requests. The service waits for a specific number of messages to be received, then processes notification requests and sends events to subscribed clients. Finally, it waits for a shutdown method call before terminating. + +![Diagram](docs/second_address_test_service.png) + +### Service consumer + +Service consumer, running on a different IP address, registers with the routing manager and discovers the service offered by the master. It subscribes to both regular and selective events, sends a series of request messages, waits for responses, then requests event notifications. After receiving all expected events, it sends a shutdown request to the service and terminates. + +![Diagram](docs/second_address_test_client.png) diff --git a/test/network_tests/start_stop_start_tests/CMakeLists.txt b/test/network_tests/start_stop_start_tests/CMakeLists.txt new file mode 100644 index 000000000..18e316853 --- /dev/null +++ b/test/network_tests/start_stop_start_tests/CMakeLists.txt @@ -0,0 +1,45 @@ +# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +cmake_minimum_required(VERSION 3.4...3.22) + +project(start_stop_start_tests LANGUAGES CXX) + +# Configure necessary files into the build folder. +set(configuration_files + start_stop_start_test.json +) +configure_files("${configuration_files}") + +# Add test executable. +add_executable(start_stop_start_test_manager + start_stop_start_test_manager.cpp +) + +# Add test executable. +add_executable(start_stop_start_test_app1 + start_stop_start_test_app1.cpp +) + +# Add test executable. +add_executable(start_stop_start_test_app2 + start_stop_start_test_app2.cpp +) + +# Add build dependencies and link libraries to executables. +set(executables + start_stop_start_test_app1 + start_stop_start_test_app2 + start_stop_start_test_manager +) +targets_add_default_dependencies("${executables}") +targets_link_default_libraries("${executables}") + +# Add custom test command. +add_custom_test( + NAME start_stop_start_test + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/start_stop_start_test_manager + TIMEOUT 60 +) diff --git a/test/network_tests/start_stop_start_tests/conf/start_stop_start_test.json.in b/test/network_tests/start_stop_start_tests/conf/start_stop_start_test.json.in new file mode 100644 index 000000000..3041b2009 --- /dev/null +++ b/test/network_tests/start_stop_start_tests/conf/start_stop_start_test.json.in @@ -0,0 +1,49 @@ +{ + "unicast":"@TEST_IP_MASTER@", + "logging": + { + "level":"trace", + "console":"true" + }, + "tracing" : + { + "enable" : "true" + }, + "applications": + [ + { + "name":"start_stop_start_test_app1", + "id":"0x1234" + }, + { + "name":"start_stop_start_test_app2", + "id":"0x1235" + }, + { + "name" : "routingmanagerd", + "id" : "0x0100" + } + ], + "routing": + { + "host": + { + "name":"routingmanagerd", + "unicast":"@TEST_IP_MASTER@", + "port":"31490" + } + }, + "service-discovery": + { + "enable":"false", + "multicast":"224.0.0.1", + "port":"30490", + "protocol":"udp", + "initial_delay_min":"10", + "initial_delay_max":"10", + "repetitions_base_delay":"30", + "repetitions_max":"3", + "cyclic_offer_delay":"20000", + "ttl":"3" + } +} diff --git a/test/network_tests/start_stop_start_tests/start_stop_start_test.md b/test/network_tests/start_stop_start_tests/start_stop_start_test.md new file mode 100644 index 000000000..e3f94c452 --- /dev/null +++ b/test/network_tests/start_stop_start_tests/start_stop_start_test.md @@ -0,0 +1,96 @@ +# Start Stop Start Network Test + +## Objective + +The purpose of the `start_stop_start_tests` is to validate the behavior of VSOMEIP applications when they are started, stopped, and started again, ensuring that the registration/deregistration cycle works correctly. + +It ensures that the application is able to start again (`app->start()`) after a stop (`app->stop()`) and correctly registers with the daemon. + +## Test Structure + +The tests consists of three components: + +- **start_stop_start_test_manager**: Orchestrates the test execution and validates inter-process communication. +- **start_stop_start_test_app1**: First application, responsible for starting, stopping, and starting again the Application 1 and manages their lifecycle. +- **start_stop_start_test_app2**: Second application, responsible for starting and stopping the Application 2 and manages their lifecycle. + +The tests uses shared memory for inter-process communication between the three components, with each application reporting its state through message queues. + +## Test Flow + +### Single Application Test (start_stop_start_one_application) + +#### Step 1: Launch Application 1 + +- Application 1 is started +- Application 1 register with the routing manager +- Application 1 signal successful registration via shared memory queues + +#### Step 2: Stop Application 1 + +- Application 1 is stopped +- Application 1 deregister with the routing manager +- Application 1 signal successful deregistration via shared memory queues + +#### Step 3: Start Application 1 again + +- Application 1 is started +- Application 1 register with the routing manager +- Application 1 signal successful registration via shared memory queues + +#### Step 4: Stop both applications + +- Both applications are gracefully stopped +- Cleanup, resource deallocation occurs and test completion + +#### Note: + +- In both **start_stop_start_test_manager** and **start_stop_start_test_app1**, steps 3 and 4 are executed as steps 4 and 5 in the code to maintain consistency between the two tests (start_stop_start_one_application and start_stop_start_two_applications). However, they are actually steps 3 and 4 because the framework inserts an empty action in between that does not execute anything. + +### Two Applications Test (start_stop_start_two_applications) + +#### Step 1: Launch Application 1 + +- Application 1 is started +- Application 1 register with the routing manager +- Application 1 signal successful registration via shared memory queues + +#### Step 2: Stop Application 1 + +- Application 1 is stopped +- Application 1 deregister with the routing manager +- Application 1 signal successful deregistration via shared memory queues + +#### Step 3: Launch Application 2 + +- Application 2 is started +- Application 2 register with the routing manager +- Application 2 signal successful registration via shared memory queues + +#### Step 4: Start Application 1 again + +- Application 1 is started +- Application 1 register with the routing manager +- Application 1 signal successful registration via shared memory queues + +#### Step 5: Stop both applications + +- Both applications are gracefully stopped +- Cleanup, resource deallocation occurs and test completion + +## Configuration + +The test uses a configuration file (`start_stop_start_test.json`) that defines: +- Applications definitions and routing configuration +- Network settings and timeouts +- Application-specific parameters + +## Timeout Configuration + +- Default timeout: 3 second for all operations +- Overall test timeout: 60 seconds (configured in CMakeLists.txt) + +## Notes + +- The test can fail at the registration phase if a registration timeout occurs. +- If any step fails, googletest is configured to throw an exception on manager, which will terminate all subprocesses. \ No newline at end of file diff --git a/test/network_tests/start_stop_start_tests/start_stop_start_test_app1.cpp b/test/network_tests/start_stop_start_tests/start_stop_start_test_app1.cpp new file mode 100644 index 000000000..b339bdc15 --- /dev/null +++ b/test/network_tests/start_stop_start_tests/start_stop_start_test_app1.cpp @@ -0,0 +1,89 @@ +// Copyright (C) 2014-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include + +#include "start_stop_start_test_globals.hpp" +#include "vsomeip/enumeration_types.hpp" +#include + +namespace start_stop_start { +namespace app1 { + +using namespace common; + +TEST(start_stop_start_test_app1, start_stop_start_application) { + // ------------------------------------------------------------------------------------------ + // SETUP + // ------------------------------------------------------------------------------------------ + auto shm = shared_memory_slave_t(std::string(start_stop_start::TEST_NAME)); + + auto& queue = shm.get_queue(start_stop_start::APP1_IDX); + + std::promise has_registered; + + auto test_app1 = base_vsip_app_builder("start_stop_start_test_app1", "RDTS") + .with_state_callback([&](vsomeip::state_type_e state) { + if (state == vsomeip::state_type_e::ST_REGISTERED) { + has_registered.set_value(true); + } + std::cout << "APPLICATION 1 GOT " + << (state == vsomeip::state_type_e::ST_DEREGISTERED ? "ST_DEREGISTERED" : "ST_REGISTERED") + << std::endl; + }) + .auto_start(false) // Application does not start automatically. + .build(); + + test_scenario_t scenario(start_stop_start::N_TEST_STEPS); + + scenario.add_step(Steps::_1, "APPLICATION 1 Launch", + [&]() { + test_app1->start(); + auto val = has_registered.get_future().get(); + std::cout << "APPLICATION 1 registered=" << val << std::endl; + EXPECT_TRUE(val); + queue.send(val ? state_t::REGISTERED : state_t::DEREGISTERED); + }) + .add_step(Steps::_2, "APPLICATION 1 Stop", + [&]() { + test_app1->only_stop(); + test_app1->join(); + // KLUDGE(dsantiago): testing on some valgrind types delays stop process, such that + // introducing this delay fixes odd, sporadic behaviours in the stop process, stabilizing the test. + // It was verified that when running the test in a loop, sporadically, in the scenario where we only have one + // application, it would break/hang without this sleep for the valgrinds, since the process is slower. I suspect + // there was still some stop process happening in the background when the application was starting again. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + has_registered = {}; + queue.send(state_t::DEREGISTERED); + }) + .add_step(Steps::_4, "APPLICATION 1 started again", + [&]() { + test_app1->only_start(); + auto val = has_registered.get_future().get(); + std::cout << "APPLICATION 1 registered again=" << val << std::endl; + EXPECT_TRUE(val); + queue.send(val ? state_t::REGISTERED : state_t::DEREGISTERED); + }) + .add_step(Steps::_5, "APPLICATION 1 Stop the application", + [&]() { + test_app1->stop(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + queue.send(state_t::STOPPED); + }) + .run(shm, "APPLICATION_1"); + + std::cout << "APPLICATION 1 TEST IS FINISHING" << std::endl; +} // TEST +} // namespace app1 +} // namespace start_stop_start + +#if defined(__linux__) || defined(ANDROID) || defined(__QNX__) +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} +#endif diff --git a/test/network_tests/start_stop_start_tests/start_stop_start_test_app2.cpp b/test/network_tests/start_stop_start_tests/start_stop_start_test_app2.cpp new file mode 100644 index 000000000..e15d99eaf --- /dev/null +++ b/test/network_tests/start_stop_start_tests/start_stop_start_test_app2.cpp @@ -0,0 +1,65 @@ +#include +#include "start_stop_start_test_globals.hpp" + +#include +#include +#include +#include +#include + +namespace start_stop_start { +namespace app2 { + +using namespace common; + +TEST(start_stop_start_test_app2, start_stop_application) { + // ------------------------------------------------------------------------------------------ + // SETUP + // ------------------------------------------------------------------------------------------ + auto shm = shared_memory_slave_t(std::string(start_stop_start::TEST_NAME)); + + auto& queue = shm.get_queue(start_stop_start::APP2_IDX); + + std::promise has_registered; + + auto test_app2 = base_vsip_app_builder("start_stop_start_test_app2", "RDTC") + .with_state_callback([&](vsomeip::state_type_e state) { + if (state == vsomeip::state_type_e::ST_REGISTERED) { + has_registered.set_value(true); + } + std::cout << "APPLICATION 2 GOT " + << (state == vsomeip::state_type_e::ST_DEREGISTERED ? "ST_DEREGISTERED" : "ST_REGISTERED") + << std::endl; + }) + .auto_start(false) // Application does not start automatically. + .build(); + + test_scenario_t scenario(start_stop_start::N_TEST_STEPS); + + scenario.add_step(Steps::_3, "APPLICATION 2 Launch", + [&]() { + test_app2->start(); + auto val = has_registered.get_future().get(); + std::cout << "APPLICATION 2 registered=" << val << std::endl; + EXPECT_TRUE(val); + queue.send(val ? state_t::REGISTERED : state_t::DEREGISTERED); + }) + .add_step(Steps::_5, "APPLICATION 2 Stop the application", + [&]() { + test_app2->stop(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + queue.send(state_t::STOPPED); + }) + .run(shm, "APPLICATION_2"); + + std::cout << "APPLICATION 2 TEST IS FINISHING" << std::endl; +} // TEST +} // namespace app2 +} // namespace start_stop_start + +#if defined(__linux__) || defined(ANDROID) || defined(__QNX__) +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} +#endif diff --git a/test/network_tests/start_stop_start_tests/start_stop_start_test_globals.hpp b/test/network_tests/start_stop_start_tests/start_stop_start_test_globals.hpp new file mode 100644 index 000000000..c8619b9a7 --- /dev/null +++ b/test/network_tests/start_stop_start_tests/start_stop_start_test_globals.hpp @@ -0,0 +1,39 @@ +// Copyright (C) 2014-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +#pragma once + +#include +#include +#include + +#include +#include + +namespace start_stop_start { + +using namespace common; + +/* clang-format on */ + +enum class state_t : uint32_t { + REGISTERED = 0x1, + NOT_REGISTERED = 0x1 << 1, + DEREGISTERED = 0x1 << 2, + + STOPPED = 0x1 << 3, +}; + +constexpr static std::string_view TEST_NAME = "StartStopStartSteps"; + +// App1 always index 0 +constexpr static int APP1_IDX = 0; +// APP2 always index 1 +constexpr static int APP2_IDX = 1; + +constexpr static int N_COMPONENTS_T1 = 2; +constexpr static int N_COMPONENTS_T2 = 3; + +constexpr static size_t N_TEST_STEPS = static_cast(Steps::_5); +} // namespace start_stop_start diff --git a/test/network_tests/start_stop_start_tests/start_stop_start_test_manager.cpp b/test/network_tests/start_stop_start_tests/start_stop_start_test_manager.cpp new file mode 100644 index 000000000..1c980f03a --- /dev/null +++ b/test/network_tests/start_stop_start_tests/start_stop_start_test_manager.cpp @@ -0,0 +1,133 @@ +// Copyright (C) 2014-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include +#include "common/interprocess.hpp" +#include "common/process.hpp" +#include "start_stop_start_test_globals.hpp" + +namespace start_stop_start { +namespace manager { + +class start_stop_start_test_manager : public base_logger { +protected: + static void SetUp() { VSOMEIP_INFO << "Setting up start_stop_start_test_manager"; } + static void TearDown() { VSOMEIP_INFO << "Tearing down start_stop_start_test_manager"; } +}; + +TEST(start_stop_start_test_manager, start_stop_start_with_one_application) { + auto shm = + shared_memory_master_t(std::string(start_stop_start::TEST_NAME), start_stop_start::N_COMPONENTS_T1); + + auto& app1_queue = shm.get_queue(start_stop_start::APP1_IDX); + int receiver_timeout_ms = 3000; + + process_group_t group; + group.define_type("application1", "./start_stop_start_test_app1", + [](auto& env) { env["VSOMEIP_CONFIGURATION"] = "start_stop_start_test.json"; }) + .add_daemon("start_stop_start_test.json") + .add_process("APPLICATION1", "application1"); + + test_scenario_t scenario(start_stop_start::N_TEST_STEPS); + + scenario.add_step(Steps::_1, "#1 - LAUNCH APPLICATION 1", + [&]() { + ASSERT_EQ(start_stop_start::state_t::REGISTERED, app1_queue.receive(receiver_timeout_ms)); + std::cout << "MANAGER GOT APPLICATION 1 REGISTERED" << std::endl; + }) + .add_step(Steps::_2, "#2 - STOP APPLICATION 1", + [&]() { + ASSERT_EQ(start_stop_start::state_t::DEREGISTERED, app1_queue.receive(receiver_timeout_ms)); + std::cout << "MANAGER GOT APPLICATION 1 DEREGISTERED" << std::endl; + }) + .add_step(Steps::_4, "#3 - START APPLICATION 1 AGAIN", + [&]() { + ASSERT_EQ(start_stop_start::state_t::REGISTERED, app1_queue.receive(receiver_timeout_ms)); + std::cout << "MANAGER GOT APPLICATION 1 REGISTERED AGAIN" << std::endl; + }) + .add_step(Steps::_5, "#4 - STOP APPLICATION 1. GOING DOWN", + [&]() { ASSERT_EQ(start_stop_start::state_t::STOPPED, app1_queue.receive(receiver_timeout_ms)); }); + try { + std::cout << "MANAGER LAUNCHED ALL PROCESSES" << std::endl; + group.start(); + std::cout << "GROUPS CONFIG SIZE" << group.configs_.size() << std::endl; + scenario.run(shm, "MANAGER"); + std::cout << "MANAGER STOPPED ALL" << std::endl; + group.stop(); + } catch (boost::interprocess::interprocess_exception& ex) { + std::cout << ex.what() << std::endl; + } +} // start_stop_start_with_one_application + +TEST(start_stop_start_test_manager, start_stop_start_with_two_application) { + auto shm = + shared_memory_master_t(std::string(start_stop_start::TEST_NAME), start_stop_start::N_COMPONENTS_T2); + + auto& app1_queue = shm.get_queue(start_stop_start::APP1_IDX); + auto& app2_queue = shm.get_queue(start_stop_start::APP2_IDX); + int receiver_timeout_ms = 3000; + + process_group_t group; + group.define_type("application1", "./start_stop_start_test_app1", + [](auto& env) { env["VSOMEIP_CONFIGURATION"] = "start_stop_start_test.json"; }) + .define_type("application2", "./start_stop_start_test_app2", + [](auto& env) { env["VSOMEIP_CONFIGURATION"] = "start_stop_start_test.json"; }) + .add_daemon("start_stop_start_test.json") + .add_process("APPLICATION1", "application1") + .add_process("APPLICATION2", "application2"); + + test_scenario_t scenario(start_stop_start::N_TEST_STEPS); + + scenario.add_step(Steps::_1, "#1 - LAUNCH APPLICATION 1", + [&]() { + ASSERT_EQ(start_stop_start::state_t::REGISTERED, app1_queue.receive(receiver_timeout_ms)); + std::cout << "MANAGER GOT APPLICATION 1 REGISTERED" << std::endl; + }) + .add_step(Steps::_2, "#2 - STOP APPLICATION 1", + [&]() { + ASSERT_EQ(start_stop_start::state_t::DEREGISTERED, app1_queue.receive(receiver_timeout_ms)); + std::cout << "MANAGER GOT APPLICATION 1 DEREGISTERED" << std::endl; + }) + .add_step(Steps::_3, "#3 - LAUNCH APPLICATION 2", + [&]() { + ASSERT_EQ(start_stop_start::state_t::REGISTERED, app2_queue.receive(receiver_timeout_ms)); + std::cout << "MANAGER GOT APPLICATION 2 REGISTERED" << std::endl; + }) + .add_step(Steps::_4, "#4 - START APPLICATION 1 AGAIN", + [&]() { + ASSERT_EQ(start_stop_start::state_t::REGISTERED, app1_queue.receive(receiver_timeout_ms)); + std::cout << "MANAGER GOT APPLICATION 1 REGISTERED AGAIN" << std::endl; + }) + .add_step(Steps::_5, "#5 - STOP BOTH APPLICATIONS", [&]() { + ASSERT_EQ(start_stop_start::state_t::STOPPED, app1_queue.receive(receiver_timeout_ms)); + ASSERT_EQ(start_stop_start::state_t::STOPPED, app2_queue.receive(receiver_timeout_ms)); + std::cout << "MANAGER GOT BOTH APPLICATIONS STOPPED" << std::endl; + }); + try { + std::cout << "MANAGER LAUNCHED ALL PROCESSES" << std::endl; + group.start(); + std::cout << "GROUPS CONFIG SIZE" << group.configs_.size() << std::endl; + scenario.run(shm, "MANAGER"); + std::cout << "MANAGER STOPPED ALL" << std::endl; + group.stop(); + } catch (boost::interprocess::interprocess_exception& ex) { + std::cout << ex.what() << std::endl; + } +} // start_stop_start_with_two_application +} // namespace manager +} // namespace start_stop_start + +#if defined(__linux__) || defined(ANDROID) || defined(__QNX__) +int main(int argc, char** argv) { + ::testing::GTEST_FLAG(throw_on_failure) = true; + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} +#endif diff --git a/test/network_tests/subscribe_notify_tests/subscribe_notify_test_one_tests.md b/test/network_tests/subscribe_notify_tests/subscribe_notify_test_one_tests.md index 61dd087b8..057318626 100644 --- a/test/network_tests/subscribe_notify_tests/subscribe_notify_test_one_tests.md +++ b/test/network_tests/subscribe_notify_tests/subscribe_notify_test_one_tests.md @@ -20,7 +20,7 @@ not initial events, are received correctly ## Test Logic -![Diagram](docs/subscribe_notify_test.png) +![Diagram](docs/subscribe_notify_test_one_tests.png) ### Service provider @@ -48,4 +48,4 @@ one notification for each event. After setting and receiving 3 payloads, the service consumer will unsubscribe to the service and repeat the whole process 2 more times, after which the test is concluded and the service consumer sends the service provider -a message to the shutdown method and shutdowns itself. \ No newline at end of file +a message to the shutdown method and shutdowns itself. diff --git a/test/unit_tests/CMakeLists.txt b/test/unit_tests/CMakeLists.txt index 701028805..8befec6eb 100644 --- a/test/unit_tests/CMakeLists.txt +++ b/test/unit_tests/CMakeLists.txt @@ -18,4 +18,5 @@ add_subdirectory(utility_utility_tests) if (NOT WIN32) add_subdirectory(netlink_tests) add_subdirectory(usei_tests) -endif() \ No newline at end of file +add_subdirectory(endpoint_tests) +endif() diff --git a/test/unit_tests/endpoint_tests/CMakeLists.txt b/test/unit_tests/endpoint_tests/CMakeLists.txt new file mode 100644 index 000000000..1f0d792f3 --- /dev/null +++ b/test/unit_tests/endpoint_tests/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +project("unit_tests_endpoint_tests" LANGUAGES CXX) + +set(TEST_SRCS + ../main.cpp + test_timer.cpp) + +# We need to set the source files directly because vsomeip doesn't export the +# timer for linking. +set( + VSIP_SRCS + ../../../implementation/endpoints/src/timer.cpp + ../../../implementation/endpoints/src/abstract_socket_factory.cpp + ../../../implementation/endpoints/src/asio_socket_factory.cpp + ../../../implementation/endpoints/src/netlink_connector.cpp +) + +add_executable( + ${PROJECT_NAME} + ${TEST_SRCS} + ${VSIP_SRCS} +) + +target_link_libraries( + ${PROJECT_NAME} + gtest + ${VSOMEIP_NAME} +) + +add_dependencies(build_unit_tests ${PROJECT_NAME}) + +add_test( + NAME ${PROJECT_NAME} + COMMAND ${PROJECT_NAME} +) diff --git a/test/unit_tests/endpoint_tests/test_timer.cpp b/test/unit_tests/endpoint_tests/test_timer.cpp new file mode 100644 index 000000000..ac86a5626 --- /dev/null +++ b/test/unit_tests/endpoint_tests/test_timer.cpp @@ -0,0 +1,316 @@ +// Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "test_timer.hpp" + +namespace vsomeip_v3::testing { +using namespace std::chrono_literals; + +std::shared_ptr test_timer_base::factory_{std::make_shared()}; + +struct test_timer_with_fake : public test_timer_base { + + test_timer_with_fake() { factory_->timer_ = std::make_unique(timer_state_); } + + uint32_t start_count() { return timer_state_->start_count_; } + std::optional interval() { return timer_state_->timeout_; } + boost::system::error_code execute_handler() { + boost::system::error_code ec; + timer_state_->handler_(ec); + return ec; + } + + boost::asio::io_context dummy_context_; + std::shared_ptr timer_state_{std::make_shared()}; +}; + +struct test_example_use_case : public test_timer_with_fake { + // Test cases with this fixture showcase the usage of the timer utility, + // but don't really test the implementation of the timer itself +}; + +TEST_F(test_example_use_case, a_const_shared_ptr_of_timer_is_thread_safe) { + class ExampleOwner : public std::enable_shared_from_this { + struct hidden { }; // private struct ensures that ::create is used for a proper setup + public: + static std::shared_ptr create(boost::asio::io_context& _io) { + auto p = std::make_shared(hidden{}, _io); + // 1. ensures that task is properly set before any actual usage of the timer. + // 2. capture this by weak_ref to avoid the need of manual destruction of the timer. + // Note: This step can not be done in the c'tor as the weak_from_this/shared_from_this call would return a nullptr at this + // point. + // Note: This two step setup is inevitable as the task of the timer and the owner of the timer form an ownership cycle (although + // only temporary due to the usage of weak_from_this). + + // the return can be ignored in this very instance, because the timer had no chance to have been + // started before. The return is given, to highlight that adjusting the timers task at a later + // stage is a dangerous operation and has a chance to fail (because the timer class itself will + // protect against the change of the task while the timer is running). + [[maybe_unused]] bool set_was_success = p->timer_->set_task([weak_ref = p->weak_from_this()] { + if (auto self = weak_ref.lock(); self) { + self->counter_ += 1; + } + return true; + }); + return p; + } + + ExampleOwner([[maybe_unused]] hidden _, boost::asio::io_context& _io) : + // create a timer with a dummy task to ensure the shared_ptr is receiving its memory in the ctor and can therefore be + // used from any thread without further inspection + timer_(timer::create(_io, 12ms, [] { return false; })) { } + + // Because the timer has a thread-safe interface, the shared_ptr to the timer is const, and the task is setup is ensured + // in a dedicated create function there is no need for synchronization primitives when using the timer itself. + void start() { timer_->start(); } + void stop() { timer_->stop(); } + + std::atomic counter_{0}; + + private: + std::shared_ptr const timer_; + }; + + auto owner = ExampleOwner::create(dummy_context_); + // start the timer + owner->start(); + ASSERT_EQ(1, start_count()); + ASSERT_EQ(2, timer_state_.use_count()); // 1 from the fixture + 1 from the timer + + // because the owner passed in a weak_ref, releasing the owner releases the timer without further action + owner = nullptr; + ASSERT_EQ(1, timer_state_.use_count()); // 1 from the fixture +} +using return_channel = std::function; +// For explanation of the ownership model refer to the test before +class TimeboxedOperation : public std::enable_shared_from_this { + struct hidden { }; + +public: + static std::shared_ptr create(boost::asio::io_context& io_, return_channel channel_) { + auto p = std::make_shared(hidden{}, io_, std::move(channel_)); + (void)p->timer_->set_task([weak_ref = p->weak_from_this()] { + if (auto self = weak_ref.lock(); self) { + self->timer_expired(); + } + return false; + }); + return p; + } + TimeboxedOperation([[maybe_unused]] hidden, boost::asio::io_context& _io, return_channel channel_) : + timer_(timer::create(_io, 12ms, [] { return false; })), answer_(std::move(channel_)) { } + + void start() { + // place holder comment. Here the async operation would be started + // that would call async_return + timer_->start(); + } + + void async_return() { + // check whether the timer task was executed already + if (c_.fetch_add(1) > 0) { + return; + } + answer_(true); + } + +private: + void timer_expired() { + // check async_return was executed already + if (c_.fetch_add(1) > 0) { + return; + } + answer_(false); + } + void answer(bool success) { answer_(success); } + std::atomic c_{0}; + std::shared_ptr const timer_; + return_channel const answer_; +}; + +static constexpr int async_finished = 1; +static constexpr int timer_expired = 2; +TEST_F(test_example_use_case, use_timer_as_timebox_async_finishes) { + int result{0}; + auto t1 = TimeboxedOperation::create(dummy_context_, [&](bool success) { result = success ? async_finished : timer_expired; }); + + // start the timer + t1->start(); + // before the timer elapses the timeboxed event occurrs: + t1->async_return(); + EXPECT_EQ(result, async_finished); +} +TEST_F(test_example_use_case, use_timer_as_timebox_timer_expires) { + int result{0}; + auto t2 = TimeboxedOperation::create(dummy_context_, [&](bool success) { result = success ? async_finished : timer_expired; }); + + // start the timer + t2->start(); + // let the timer expire + execute_handler(); + EXPECT_EQ(result, timer_expired); +} + +TEST_F(test_timer_with_fake, init) { + auto timer = timer::create(dummy_context_, 12ms, [] { return false; }); +} +TEST_F(test_timer_with_fake, starting_a_timer_will_set_the_interval_and_wait) { + auto input = 2min; // arbitrary + auto timer = timer::create(dummy_context_, input, [] { return false; }); + + ASSERT_EQ(0, start_count()); + timer->start(); + + EXPECT_EQ(input, interval()); + EXPECT_EQ(1, start_count()); +} +TEST_F(test_timer_with_fake, a_one_shot_timer_is_not_restarted) { + auto timer = timer::create(dummy_context_, 2s, [] { return false; }); + ASSERT_EQ(0, start_count()); + timer->start(); + ASSERT_EQ(1, start_count()); + + execute_handler(); + + EXPECT_EQ(1, start_count()); +} +TEST_F(test_timer_with_fake, a_one_shot_timer_that_starts_itself_is_restarted) { + std::shared_ptr timer; + timer = timer::create(dummy_context_, 2s, [&] { + timer->start(); + return false; + }); + ASSERT_EQ(0, start_count()); + timer->start(); + ASSERT_EQ(1, start_count()); + + execute_handler(); + + EXPECT_EQ(2, start_count()); +} +TEST_F(test_timer_with_fake, a_one_shot_timer_that_is_started_twice_is_restarted) { + auto timer = timer::create(dummy_context_, 2s, [] { return false; }); + ASSERT_EQ(0, start_count()); + timer->start(); + ASSERT_EQ(1, start_count()); + timer->start(); + ASSERT_EQ(2, start_count()); + + execute_handler(); + + EXPECT_EQ(2, start_count()); +} +TEST_F(test_timer_with_fake, a_repeating_timer_is_restarted) { + auto timer = timer::create(dummy_context_, 2s, [] { return true; }); + ASSERT_EQ(0, start_count()); + timer->start(); + ASSERT_EQ(1, start_count()); + + for (int i = 0; i < 6; ++i) { + execute_handler(); + EXPECT_EQ(i + 2, start_count()); + } +} +TEST_F(test_timer_with_fake, a_timer_that_stops_itself_within_the_task_is_not_restarted) { + std::shared_ptr timer; + // notice that the timer would be restarted, if the stop would not be processed + timer = timer::create(dummy_context_, 2s, [&] { + timer->stop(); + return true; + }); + + ASSERT_EQ(0, start_count()); + timer->start(); + ASSERT_EQ(1, start_count()); + execute_handler(); + + EXPECT_EQ(1, start_count()); +} + +struct test_timer_with_asio : public test_timer_base { + test_timer_with_asio() { factory_->timer_ = std::make_unique(io_); } + boost::asio::io_context io_; +}; + +TEST_F(test_timer_with_asio, a_timer_can_be_restarted_before_the_cancellation_is_internally_handled) { + + uint32_t execution_count{0}; + auto timer = timer::create(io_, 500ms /*arbitrary, but big enough that start, stop sequence below is done before expiration*/, [&] { + ++execution_count; + return true; + }); + timer->start(); + timer->stop(); + timer->start(); + io_.poll_one(); + std::this_thread::sleep_for(501ms); + io_.poll_one(); + EXPECT_EQ(1, execution_count); +} + +TEST_F(test_timer_with_asio, a_stopped_timer_will_not_execute_the_handler) { + uint32_t execution_count{0}; + auto timer = timer::create(io_, 10ms, [&] { + ++execution_count; + return true; + }); + + ASSERT_EQ(0, execution_count); + timer->start(); + std::this_thread::sleep_for(11ms); + io_.poll_one(); + ASSERT_EQ(1, execution_count); + std::this_thread::sleep_for(11ms); + io_.poll_one(); + ASSERT_EQ(2, execution_count); + std::this_thread::sleep_for(11ms); + io_.poll_one(); + ASSERT_EQ(3, execution_count); + timer->stop(); + std::this_thread::sleep_for(11ms); + io_.poll_one(); + + EXPECT_EQ(3, execution_count); +} + +TEST_F(test_timer_with_asio, a_timer_stopped_during_execution_from_another_thread_is_not_restarted) { + std::mutex mtx; + std::condition_variable cv; + int step{0}; + uint32_t execution_count{0}; + auto timeout = 5ms; + + // the timer notifies the stopper to stop the timer and waits for the stop + // to have been called. + auto timer = timer::create(io_, timeout, [&] { + std::unique_lock lock{mtx}; + ++execution_count; + ++step; + cv.notify_one(); + lock.unlock(); + cv.wait_for(lock, 5s, [&] { return step == 2; }); + return true; + }); + + auto stopper = std::thread([&] { + std::unique_lock lock{mtx}; + cv.wait_for(lock, 5s, [&] { return step == 1; }); + timer->stop(); + ++step; + cv.notify_one(); + }); + + timer->start(); + std::this_thread::sleep_for(timeout + 5ms); + io_.poll_one(); + std::this_thread::sleep_for(timeout + 5ms); + io_.poll_one(); + + EXPECT_EQ(execution_count, 1); + if (stopper.joinable()) { + stopper.join(); + } +} +} diff --git a/test/unit_tests/endpoint_tests/test_timer.hpp b/test/unit_tests/endpoint_tests/test_timer.hpp new file mode 100644 index 000000000..5bce9cb23 --- /dev/null +++ b/test/unit_tests/endpoint_tests/test_timer.hpp @@ -0,0 +1,69 @@ +// Copyright (C) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef VSOMEIP_V3_TIMER_TESTING_ +#define VSOMEIP_V3_TIMER_TESTING_ + +#include +#include +#include + +#include +#include +#include +#include + +#include "../../../implementation/endpoints/include/timer.hpp" +#include "../../../implementation/endpoints/include/asio_timer.hpp" +#include "../../../implementation/endpoints/include/abstract_socket_factory.hpp" +#include "../../../implementation/endpoints/include/abstract_netlink_connector.hpp" +#include "../../../implementation/endpoints/include/abstract_timer.hpp" + +namespace vsomeip_v3::testing { + +struct timer_state { + uint32_t cancel_count_{0}; + uint32_t start_count_{0}; + std::optional timeout_; + abstract_timer::handler_t handler_; +}; + +class fake_timer : public abstract_timer { +public: + fake_timer(std::shared_ptr _state) : state_(std::move(_state)) { } + virtual void cancel() override { ++state_->cancel_count_; } + + virtual void expires_after(std::chrono::milliseconds _timeout) override { state_->timeout_ = _timeout; } + + virtual void async_wait(handler_t _handler) { + state_->handler_ = std::move(_handler); + ++state_->start_count_; + } + + std::shared_ptr state_; +}; + +class fake_factory : public abstract_socket_factory { +public: + std::shared_ptr create_netlink_connector(boost::asio::io_context&, const boost::asio::ip::address&, + const boost::asio::ip::address&, bool) override { + return nullptr; + } + + virtual std::unique_ptr create_tcp_socket(boost::asio::io_context&) override { return nullptr; } + virtual std::unique_ptr create_tcp_acceptor(boost::asio::io_context&) override { return nullptr; } + virtual std::unique_ptr create_timer(boost::asio::io_context&) override { return std::move(timer_); } + + // assumed to be filled by the fixtures. + std::unique_ptr timer_; +}; + +struct test_timer_base : ::testing::Test { + + static void SetUpTestSuite() { vsomeip_v3::set_abstract_factory(factory_); } + static std::shared_ptr factory_; +}; +} +#endif diff --git a/test/unit_tests/routing_manager_tests/routing_manager_ut_setup.hpp b/test/unit_tests/routing_manager_tests/routing_manager_ut_setup.hpp index 20d8df732..4a99a0430 100644 --- a/test/unit_tests/routing_manager_tests/routing_manager_ut_setup.hpp +++ b/test/unit_tests/routing_manager_tests/routing_manager_ut_setup.hpp @@ -6,8 +6,9 @@ #ifndef ROUTING_MANAGER_UT_SETUP_HPP #define ROUTING_MANAGER_UT_SETUP_HPP +#include + #include -#include #include #include diff --git a/test/unit_tests/usei_tests/mocked_vsomeip_dependencies.hpp b/test/unit_tests/usei_tests/mocked_vsomeip_dependencies.hpp index 3e6a28c23..79a0abb81 100644 --- a/test/unit_tests/usei_tests/mocked_vsomeip_dependencies.hpp +++ b/test/unit_tests/usei_tests/mocked_vsomeip_dependencies.hpp @@ -26,15 +26,9 @@ template vsomeip_v3::endpoint_impl::endpoint_impl(const std::shared_ptr& _endpoint_host, const std::shared_ptr& _routing_host, boost::asio::io_context& _io, const std::shared_ptr& _configuration) : - io_(_io), endpoint_host_(_endpoint_host), routing_host_(_routing_host), is_supporting_magic_cookies_(false), - has_enabled_magic_cookies_(false), use_count_(0), sending_blocked_(false), configuration_(_configuration), + io_(_io), endpoint_host_(_endpoint_host), routing_host_(_routing_host), sending_blocked_(false), configuration_(_configuration), is_supporting_someip_tp_(false) { } -template -void vsomeip_v3::endpoint_impl::enable_magic_cookies() { - has_enabled_magic_cookies_ = is_supporting_magic_cookies_; -} - template uint32_t vsomeip_v3::endpoint_impl::find_magic_cookie(byte_t* /*_buffer*/, size_t /*_size*/) { return 0xFFFFFFFF; @@ -69,7 +63,7 @@ void vsomeip_v3::server_endpoint_impl::prepare_stop(const endpoint::pr service_t /*_service*/) { } template -void vsomeip_v3::server_endpoint_impl::stop() { } +void vsomeip_v3::server_endpoint_impl::stop(bool /*_due_to_error*/) { } template bool vsomeip_v3::server_endpoint_impl::is_client() const { @@ -79,7 +73,7 @@ bool vsomeip_v3::server_endpoint_impl::is_client() const { template void vsomeip_v3::server_endpoint_impl::restart(bool /*_force*/) { boost::system::error_code its_error; - this->stop(); + this->stop(false); this->init(server_endpoint_impl::local_, its_error); this->start(); } @@ -229,7 +223,7 @@ struct mock_routing_host : public vsomeip_v3::routing_host { MOCK_METHOD3(add_guest, void(vsomeip_v3::client_t _client, const boost::asio::ip::address& _address, vsomeip_v3::port_t _port)); MOCK_METHOD2(add_known_client, void(vsomeip_v3::client_t _client, const std::string& _client_host)); MOCK_CONST_METHOD2(get_guest_by_address, vsomeip_v3::client_t(const boost::asio::ip::address& _address, vsomeip_v3::port_t _port)); - MOCK_METHOD2(remove_local, void(vsomeip_v3::client_t _client, bool _remove_sec_client)); + MOCK_METHOD3(remove_local, void(vsomeip_v3::client_t _client, bool _remove_sec_client, bool _remove_due_to_error)); MOCK_CONST_METHOD1(get_env, std::string(vsomeip_v3::client_t _client)); MOCK_METHOD3(remove_subscriptions, void(vsomeip_v3::port_t _local_port, const boost::asio::ip::address& _remote_address, vsomeip_v3::port_t _remote_port)); diff --git a/test/unit_tests/usei_tests/ut_basic_tests.cpp b/test/unit_tests/usei_tests/ut_basic_tests.cpp index 8d9ecf79e..5519d0364 100644 --- a/test/unit_tests/usei_tests/ut_basic_tests.cpp +++ b/test/unit_tests/usei_tests/ut_basic_tests.cpp @@ -136,7 +136,7 @@ TEST_F(usei_fixture, basic) { std::unique_lock lock(sync); EXPECT_EQ(event.wait_for(lock, 5s, [&] { return received; }), true); - server_->stop(); + server_->stop(false); } TEST_F(usei_fixture, corrupted_data) { @@ -178,7 +178,7 @@ TEST_F(usei_fixture, corrupted_data) { std::unique_lock lock(sync); EXPECT_EQ(event.wait_for(lock, 5s, [&] { return received; }), true); - server_->stop(); + server_->stop(false); } TEST_F(usei_fixture, basic_multicast) { @@ -209,7 +209,7 @@ TEST_F(usei_fixture, basic_multicast) { server_->joined_.clear(); // We don't want to call `leave` method, so we do its job server_->set_multicast_option(multicast_parameters_.address(), false, error); - server_->stop(); + server_->stop(false); } TEST_F(usei_fixture, no_overwrite_during_restart) { @@ -241,7 +241,7 @@ TEST_F(usei_fixture, no_overwrite_during_restart) { } producer.join(); - server_->stop(); + server_->stop(false); } TEST_F(usei_fixture, no_overwrite_during_restart_with_multicast) { @@ -281,5 +281,5 @@ TEST_F(usei_fixture, no_overwrite_during_restart_with_multicast) { server_->joined_.clear(); server_->set_multicast_option(multicast_parameters_.address(), false, error); producer.join(); - server_->stop(); + server_->stop(false); } diff --git a/zuul/network-tests/shlib/results.shlib b/zuul/network-tests/shlib/results.shlib index 41473bcd0..0aaead96c 100644 --- a/zuul/network-tests/shlib/results.shlib +++ b/zuul/network-tests/shlib/results.shlib @@ -47,14 +47,18 @@ function valgrind-errors { local outputs_folder="$1" local test_name="$2" + local result=0 - output_file="$outputs_folder/$test_name.out" - if [[ ! -f "$output_file" || -d "$output_file" ]] - then - echo 0 - return - fi - (grep -oP 'ERROR SUMMARY: \K\d+' "$output_file" || echo 0) | sort -n | tail -n1 + ## write all of the valgrind logs into a single output file, for ease of check + cat "$outputs_folder/$test_name".*.out > "$outputs_folder/$test_name".combined.out + + for output_file in "$outputs_folder/$test_name".*.out; do + if [[ -f "$output_file" ]] + then + result=$(($result + $((grep -oP 'ERROR SUMMARY: \K\d+' "$output_file" || echo 0) | sort -n | tail -n1))) + fi + done + echo $result } function gen-core-backtraces